/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text;

import com.sun.beans.util.Cache;

import java.security.AccessController;
import java.security.PrivilegedAction;

import java.beans.Transient;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;

import java.util.concurrent.*;

import java.io.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.awt.datatransfer.*;
import java.awt.im.InputContext;
import java.awt.im.InputMethodRequests;
import java.awt.font.TextHitInfo;
import java.awt.font.TextAttribute;

import java.awt.print.Printable;
import java.awt.print.PrinterException;

import javax.print.PrintService;
import javax.print.attribute.PrintRequestAttributeSet;

import java.text.*;
import java.text.AttributedCharacterIterator.Attribute;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;

import javax.accessibility.*;

import javax.print.attribute.*;

import sun.awt.AppContext;


import sun.swing.PrintingStatus;
import sun.swing.SwingUtilities2;
import sun.swing.text.TextComponentPrintable;
import sun.swing.SwingAccessor;

/**
 * <code>JTextComponent</code> is the base class for swing text components.  It tries to be
 * compatible with the <code>java.awt.TextComponent</code> class where it can reasonably do so.
 * Also provided are other services for additional flexibility (beyond the pluggable UI and bean
 * support). You can find information on how to use the functionality this class provides in <a
 * href="https://docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html">General Rules
 * for Using Text Components</a>, a section in <em>The Java Tutorial.</em>
 *
 * <dl> <dt><b><font size=+1>Caret Changes</font></b> <dd> The caret is a pluggable object in swing
 * text components. Notification of changes to the caret position and the selection are sent to
 * implementations of the <code>CaretListener</code> interface that have been registered with the
 * text component. The UI will install a default caret unless a customized caret has been set. <br>
 * By default the caret tracks all the document changes performed on the Event Dispatching Thread
 * and updates it's position accordingly if an insertion occurs before or at the caret position or a
 * removal occurs before the caret position. <code>DefaultCaret</code> tries to make itself visible
 * which may lead to scrolling of a text component within <code>JScrollPane</code>. The default
 * caret behavior can be changed by the {@link DefaultCaret#setUpdatePolicy} method. <br>
 * <b>Note</b>: Non-editable text components also have a caret though it may not be painted.
 *
 * <dt><b><font size=+1>Commands</font></b> <dd> Text components provide a number of commands that
 * can be used to manipulate the component.  This is essentially the way that the component
 * expresses its capabilities.  These are expressed in terms of the swing <code>Action</code>
 * interface, using the <code>TextAction</code> implementation. The set of commands supported by the
 * text component can be found with the {@link #getActions} method.  These actions can be bound to
 * key events, fired from buttons, etc.
 *
 * <dt><b><font size=+1>Text Input</font></b> <dd> The text components support flexible and
 * internationalized text input, using keymaps and the input method framework, while maintaining
 * compatibility with the AWT listener model. <p> A {@link javax.swing.text.Keymap} lets an
 * application bind key strokes to actions. In order to allow keymaps to be shared across multiple
 * text components, they can use actions that extend <code>TextAction</code>.
 * <code>TextAction</code> can determine which <code>JTextComponent</code> most recently has or had
 * focus and therefore is the subject of the action (In the case that the <code>ActionEvent</code>
 * sent to the action doesn't contain the target text component as its source). <p> The <a
 * href="../../../../technotes/guides/imf/spec.html">input method framework</a> lets text components
 * interact with input methods, separate software components that preprocess events to let users
 * enter thousands of different characters using keyboards with far fewer keys.
 * <code>JTextComponent</code> is an <em>active client</em> of the framework, so it implements the
 * preferred user interface for interacting with input methods. As a consequence, some key events do
 * not reach the text component because they are handled by an input method, and some text input
 * reaches the text component as committed text within an {@link java.awt.event.InputMethodEvent}
 * instead of as a key event. The complete text input is the combination of the characters in
 * <code>keyTyped</code> key events and committed text in input method events. <p> The AWT listener
 * model lets applications attach event listeners to components in order to bind events to actions.
 * Swing encourages the use of keymaps instead of listeners, but maintains compatibility with
 * listeners by giving the listeners a chance to steal an event by consuming it. <p> Keyboard event
 * and input method events are handled in the following stages, with each stage capable of consuming
 * the event:
 *
 * <table border=1 summary="Stages of keyboard and input method event handling"> <tr> <th
 * id="stage"><p style="text-align:left">Stage</p></th> <th id="ke"><p
 * style="text-align:left">KeyEvent</p></th> <th id="ime"><p style="text-align:left">InputMethodEvent</p></th></tr>
 * <tr><td headers="stage">1.   </td> <td headers="ke">input methods </td> <td
 * headers="ime">(generated here)</td></tr> <tr><td headers="stage">2.   </td> <td
 * headers="ke">focus manager </td> <td headers="ime"></td> </tr> <tr> <td headers="stage">3.
 * </td> <td headers="ke">registered key listeners</td> <td headers="ime">registered input method
 * listeners</tr> <tr> <td headers="stage">4.   </td> <td headers="ke"></td> <td headers="ime">input
 * method handling in JTextComponent</tr> <tr> <td headers="stage">5.   </td><td headers="ke ime"
 * colspan=2>keymap handling using the current keymap</td></tr> <tr><td headers="stage">6.
 * </td><td headers="ke">keyboard handling in JComponent (e.g. accelerators, component navigation,
 * etc.)</td> <td headers="ime"></td></tr> </table>
 *
 * <p> To maintain compatibility with applications that listen to key events but are not aware of
 * input method events, the input method handling in stage 4 provides a compatibility mode for
 * components that do not process input method events. For these components, the committed text is
 * converted to keyTyped key events and processed in the key event pipeline starting at stage 3
 * instead of in the input method event pipeline. <p> By default the component will create a keymap
 * (named <b>DEFAULT_KEYMAP</b>) that is shared by all JTextComponent instances as the default
 * keymap. Typically a look-and-feel implementation will install a different keymap that resolves to
 * the default keymap for those bindings not found in the different keymap. The minimal bindings
 * include: <ul> <li>inserting content into the editor for the printable keys. <li>removing content
 * with the backspace and del keys. <li>caret movement forward and backward </ul>
 *
 * <dt><b><font size=+1>Model/View Split</font></b> <dd> The text components have a model-view
 * split.  A text component pulls together the objects used to represent the model, view, and
 * controller. The text document model may be shared by other views which act as observers of the
 * model (e.g. a document may be shared by multiple components).
 *
 * <p style="text-align:center"><img src="doc-files/editor.gif" alt="Diagram showing interaction
 * between Controller, Document, events, and ViewFactory" HEIGHT=358 WIDTH=587></p>
 *
 * <p> The model is defined by the {@link Document} interface. This is intended to provide a
 * flexible text storage mechanism that tracks change during edits and can be extended to more
 * sophisticated models.  The model interfaces are meant to capture the capabilities of expression
 * given by SGML, a system used to express a wide variety of content. Each modification to the
 * document causes notification of the details of the change to be sent to all observers in the form
 * of a {@link DocumentEvent} which allows the views to stay up to date with the model. This event
 * is sent to observers that have implemented the {@link DocumentListener} interface and registered
 * interest with the model being observed.
 *
 * <dt><b><font size=+1>Location Information</font></b> <dd> The capability of determining the
 * location of text in the view is provided.  There are two methods, {@link #modelToView} and {@link
 * #viewToModel} for determining this information.
 *
 * <dt><b><font size=+1>Undo/Redo support</font></b> <dd> Support for an edit history mechanism is
 * provided to allow undo/redo operations.  The text component does not itself provide the history
 * buffer by default, but does provide the <code>UndoableEdit</code> records that can be used in
 * conjunction with a history buffer to provide the undo/redo support. The support is provided by
 * the Document model, which allows one to attach UndoableEditListener implementations.
 *
 * <dt><b><font size=+1>Thread Safety</font></b> <dd> The swing text components provide some support
 * of thread safe operations.  Because of the high level of configurability of the text components,
 * it is possible to circumvent the protection provided.  The protection primarily comes from the
 * model, so the documentation of <code>AbstractDocument</code> describes the assumptions of the
 * protection provided. The methods that are safe to call asynchronously are marked with comments.
 *
 * <dt><b><font size=+1>Newlines</font></b> <dd> For a discussion on how newlines are handled, see
 * <a href="DefaultEditorKit.html">DefaultEditorKit</a>.
 *
 *
 * <dt><b><font size=+1>Printing support</font></b> <dd> Several {@link #print print} methods are
 * provided for basic document printing.  If more advanced printing is needed, use the {@link
 * #getPrintable} method. </dl>
 *
 * <p> <strong>Warning:</strong> Serialized objects of this class will not be compatible with future
 * Swing releases. The current serialization support is appropriate for short term storage or RMI
 * between applications running the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade; has been added to the <code>java.beans</code> package. Please see {@link
 * java.beans.XMLEncoder}.
 *
 * @author Timothy Prinzing
 * @author Igor Kushnirskiy (printing support)
 * @beaninfo attribute: isContainer false
 * @see Document
 * @see DocumentEvent
 * @see DocumentListener
 * @see Caret
 * @see CaretEvent
 * @see CaretListener
 * @see TextUI
 * @see View
 * @see ViewFactory
 */
public abstract class JTextComponent extends JComponent implements Scrollable, Accessible {

  /**
   * Creates a new <code>JTextComponent</code>.
   * Listeners for caret events are established, and the pluggable
   * UI installed.  The component is marked as editable.  No layout manager
   * is used, because layout is managed by the view subsystem of text.
   * The document model is set to <code>null</code>.
   */
  public JTextComponent() {
    super();
    // enable InputMethodEvent for on-the-spot pre-editing
    enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.INPUT_METHOD_EVENT_MASK);
    caretEvent = new MutableCaretEvent(this);
    addMouseListener(caretEvent);
    addFocusListener(caretEvent);
    setEditable(true);
    setDragEnabled(false);
    setLayout(null); // layout is managed by View hierarchy
    updateUI();
  }

  /**
   * Fetches the user-interface factory for this text-oriented editor.
   *
   * @return the factory
   */
  public TextUI getUI() {
    return (TextUI) ui;
  }

  /**
   * Sets the user-interface factory for this text-oriented editor.
   *
   * @param ui the factory
   */
  public void setUI(TextUI ui) {
    super.setUI(ui);
  }

  /**
   * Reloads the pluggable UI.  The key used to fetch the
   * new interface is <code>getUIClassID()</code>.  The type of
   * the UI is <code>TextUI</code>.  <code>invalidate</code>
   * is called after setting the UI.
   */
  public void updateUI() {
    setUI((TextUI) UIManager.getUI(this));
    invalidate();
  }

  /**
   * Adds a caret listener for notification of any changes
   * to the caret.
   *
   * @param listener the listener to be added
   * @see javax.swing.event.CaretEvent
   */
  public void addCaretListener(CaretListener listener) {
    listenerList.add(CaretListener.class, listener);
  }

  /**
   * Removes a caret listener.
   *
   * @param listener the listener to be removed
   * @see javax.swing.event.CaretEvent
   */
  public void removeCaretListener(CaretListener listener) {
    listenerList.remove(CaretListener.class, listener);
  }

  /**
   * Returns an array of all the caret listeners
   * registered on this text component.
   *
   * @return all of this component's <code>CaretListener</code>s or an empty array if no caret
   * listeners are currently registered
   * @see #addCaretListener
   * @see #removeCaretListener
   * @since 1.4
   */
  public CaretListener[] getCaretListeners() {
    return listenerList.getListeners(CaretListener.class);
  }

  /**
   * Notifies all listeners that have registered interest for
   * notification on this event type.  The event instance
   * is lazily created using the parameters passed into
   * the fire method.  The listener list is processed in a
   * last-to-first manner.
   *
   * @param e the event
   * @see EventListenerList
   */
  protected void fireCaretUpdate(CaretEvent e) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == CaretListener.class) {
        ((CaretListener) listeners[i + 1]).caretUpdate(e);
      }
    }
  }

  /**
   * Associates the editor with a text document.
   * The currently registered factory is used to build a view for
   * the document, which gets displayed by the editor after revalidation.
   * A PropertyChange event ("document") is propagated to each listener.
   *
   * @param doc the document to display/edit
   * @beaninfo description: the text document model bound: true expert: true
   * @see #getDocument
   */
  public void setDocument(Document doc) {
    Document old = model;

        /*
         * acquire a read lock on the old model to prevent notification of
         * mutations while we disconnecting the old model.
         */
    try {
      if (old instanceof AbstractDocument) {
        ((AbstractDocument) old).readLock();
      }
      if (accessibleContext != null) {
        model.removeDocumentListener(
            ((AccessibleJTextComponent) accessibleContext));
      }
      if (inputMethodRequestsHandler != null) {
        model.removeDocumentListener((DocumentListener) inputMethodRequestsHandler);
      }
      model = doc;

      // Set the document's run direction property to match the
      // component's ComponentOrientation property.
      Boolean runDir = getComponentOrientation().isLeftToRight()
          ? TextAttribute.RUN_DIRECTION_LTR
          : TextAttribute.RUN_DIRECTION_RTL;
      if (runDir != doc.getProperty(TextAttribute.RUN_DIRECTION)) {
        doc.putProperty(TextAttribute.RUN_DIRECTION, runDir);
      }
      firePropertyChange("document", old, doc);
    } finally {
      if (old instanceof AbstractDocument) {
        ((AbstractDocument) old).readUnlock();
      }
    }

    revalidate();
    repaint();
    if (accessibleContext != null) {
      model.addDocumentListener(
          ((AccessibleJTextComponent) accessibleContext));
    }
    if (inputMethodRequestsHandler != null) {
      model.addDocumentListener((DocumentListener) inputMethodRequestsHandler);
    }
  }

  /**
   * Fetches the model associated with the editor.  This is
   * primarily for the UI to get at the minimal amount of
   * state required to be a text editor.  Subclasses will
   * return the actual type of the model which will typically
   * be something that extends Document.
   *
   * @return the model
   */
  public Document getDocument() {
    return model;
  }

  // Override of Component.setComponentOrientation
  public void setComponentOrientation(ComponentOrientation o) {
    // Set the document's run direction property to match the
    // ComponentOrientation property.
    Document doc = getDocument();
    if (doc != null) {
      Boolean runDir = o.isLeftToRight()
          ? TextAttribute.RUN_DIRECTION_LTR
          : TextAttribute.RUN_DIRECTION_RTL;
      doc.putProperty(TextAttribute.RUN_DIRECTION, runDir);
    }
    super.setComponentOrientation(o);
  }

  /**
   * Fetches the command list for the editor.  This is
   * the list of commands supported by the plugged-in UI
   * augmented by the collection of commands that the
   * editor itself supports.  These are useful for binding
   * to events, such as in a keymap.
   *
   * @return the command list
   */
  public Action[] getActions() {
    return getUI().getEditorKit(this).getActions();
  }

  /**
   * Sets margin space between the text component's border
   * and its text.  The text component's default <code>Border</code>
   * object will use this value to create the proper margin.
   * However, if a non-default border is set on the text component,
   * it is that <code>Border</code> object's responsibility to create the
   * appropriate margin space (else this property will effectively
   * be ignored).  This causes a redraw of the component.
   * A PropertyChange event ("margin") is sent to all listeners.
   *
   * @param m the space between the border and the text
   * @beaninfo description: desired space between the border and text area bound: true
   */
  public void setMargin(Insets m) {
    Insets old = margin;
    margin = m;
    firePropertyChange("margin", old, m);
    invalidate();
  }

  /**
   * Returns the margin between the text component's border and
   * its text.
   *
   * @return the margin
   */
  public Insets getMargin() {
    return margin;
  }

  /**
   * Sets the <code>NavigationFilter</code>. <code>NavigationFilter</code>
   * is used by <code>DefaultCaret</code> and the default cursor movement
   * actions as a way to restrict the cursor movement.
   *
   * @since 1.4
   */
  public void setNavigationFilter(NavigationFilter filter) {
    navigationFilter = filter;
  }

  /**
   * Returns the <code>NavigationFilter</code>. <code>NavigationFilter</code>
   * is used by <code>DefaultCaret</code> and the default cursor movement
   * actions as a way to restrict the cursor movement. A null return value
   * implies the cursor movement and selection should not be restricted.
   *
   * @return the NavigationFilter
   * @since 1.4
   */
  public NavigationFilter getNavigationFilter() {
    return navigationFilter;
  }

  /**
   * Fetches the caret that allows text-oriented navigation over
   * the view.
   *
   * @return the caret
   */
  @Transient
  public Caret getCaret() {
    return caret;
  }

  /**
   * Sets the caret to be used.  By default this will be set
   * by the UI that gets installed.  This can be changed to
   * a custom caret if desired.  Setting the caret results in a
   * PropertyChange event ("caret") being fired.
   *
   * @param c the caret
   * @beaninfo description: the caret used to select/navigate bound: true expert: true
   * @see #getCaret
   */
  public void setCaret(Caret c) {
    if (caret != null) {
      caret.removeChangeListener(caretEvent);
      caret.deinstall(this);
    }
    Caret old = caret;
    caret = c;
    if (caret != null) {
      caret.install(this);
      caret.addChangeListener(caretEvent);
    }
    firePropertyChange("caret", old, caret);
  }

  /**
   * Fetches the object responsible for making highlights.
   *
   * @return the highlighter
   */
  public Highlighter getHighlighter() {
    return highlighter;
  }

  /**
   * Sets the highlighter to be used.  By default this will be set
   * by the UI that gets installed.  This can be changed to
   * a custom highlighter if desired.  The highlighter can be set to
   * <code>null</code> to disable it.
   * A PropertyChange event ("highlighter") is fired
   * when a new highlighter is installed.
   *
   * @param h the highlighter
   * @beaninfo description: object responsible for background highlights bound: true expert: true
   * @see #getHighlighter
   */
  public void setHighlighter(Highlighter h) {
    if (highlighter != null) {
      highlighter.deinstall(this);
    }
    Highlighter old = highlighter;
    highlighter = h;
    if (highlighter != null) {
      highlighter.install(this);
    }
    firePropertyChange("highlighter", old, h);
  }

  /**
   * Sets the keymap to use for binding events to
   * actions.  Setting to <code>null</code> effectively disables
   * keyboard input.
   * A PropertyChange event ("keymap") is fired when a new keymap
   * is installed.
   *
   * @param map the keymap
   * @beaninfo description: set of key event to action bindings to use bound: true
   * @see #getKeymap
   */
  public void setKeymap(Keymap map) {
    Keymap old = keymap;
    keymap = map;
    firePropertyChange("keymap", old, keymap);
    updateInputMap(old, map);
  }

  /**
   * Turns on or off automatic drag handling. In order to enable automatic
   * drag handling, this property should be set to {@code true}, and the
   * component's {@code TransferHandler} needs to be {@code non-null}.
   * The default value of the {@code dragEnabled} property is {@code false}.
   * <p>
   * The job of honoring this property, and recognizing a user drag gesture,
   * lies with the look and feel implementation, and in particular, the component's
   * {@code TextUI}. When automatic drag handling is enabled, most look and
   * feels (including those that subclass {@code BasicLookAndFeel}) begin a
   * drag and drop operation whenever the user presses the mouse button over
   * a selection and then moves the mouse a few pixels. Setting this property to
   * {@code true} can therefore have a subtle effect on how selections behave.
   * <p>
   * If a look and feel is used that ignores this property, you can still
   * begin a drag and drop operation by calling {@code exportAsDrag} on the
   * component's {@code TransferHandler}.
   *
   * @param b whether or not to enable automatic drag handling
   * @throws HeadlessException if <code>b</code> is <code>true</code> and
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>
   * @beaninfo description: determines whether automatic drag handling is enabled bound: false
   * @see java.awt.GraphicsEnvironment#isHeadless
   * @see #getDragEnabled
   * @see #setTransferHandler
   * @see TransferHandler
   * @since 1.4
   */
  public void setDragEnabled(boolean b) {
    if (b && GraphicsEnvironment.isHeadless()) {
      throw new HeadlessException();
    }
    dragEnabled = b;
  }

  /**
   * Returns whether or not automatic drag handling is enabled.
   *
   * @return the value of the {@code dragEnabled} property
   * @see #setDragEnabled
   * @since 1.4
   */
  public boolean getDragEnabled() {
    return dragEnabled;
  }

  /**
   * Sets the drop mode for this component. For backward compatibility,
   * the default for this property is <code>DropMode.USE_SELECTION</code>.
   * Usage of <code>DropMode.INSERT</code> is recommended, however,
   * for an improved user experience. It offers similar behavior of dropping
   * between text locations, but does so without affecting the actual text
   * selection and caret location.
   * <p>
   * <code>JTextComponents</code> support the following drop modes:
   * <ul>
   * <li><code>DropMode.USE_SELECTION</code></li>
   * <li><code>DropMode.INSERT</code></li>
   * </ul>
   * <p>
   * The drop mode is only meaningful if this component has a
   * <code>TransferHandler</code> that accepts drops.
   *
   * @param dropMode the drop mode to use
   * @throws IllegalArgumentException if the drop mode is unsupported or <code>null</code>
   * @see #getDropMode
   * @see #getDropLocation
   * @see #setTransferHandler
   * @see javax.swing.TransferHandler
   * @since 1.6
   */
  public final void setDropMode(DropMode dropMode) {
    if (dropMode != null) {
      switch (dropMode) {
        case USE_SELECTION:
        case INSERT:
          this.dropMode = dropMode;
          return;
      }
    }

    throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for text");
  }

  /**
   * Returns the drop mode for this component.
   *
   * @return the drop mode for this component
   * @see #setDropMode
   * @since 1.6
   */
  public final DropMode getDropMode() {
    return dropMode;
  }

  static {
    SwingAccessor.setJTextComponentAccessor(
        new SwingAccessor.JTextComponentAccessor() {
          public TransferHandler.DropLocation dropLocationForPoint(JTextComponent textComp,
              Point p) {
            return textComp.dropLocationForPoint(p);
          }

          public Object setDropLocation(JTextComponent textComp,
              TransferHandler.DropLocation location,
              Object state, boolean forDrop) {
            return textComp.setDropLocation(location, state, forDrop);
          }
        });
  }


  /**
   * Calculates a drop location in this component, representing where a
   * drop at the given point should insert data.
   * <p>
   * Note: This method is meant to override
   * <code>JComponent.dropLocationForPoint()</code>, which is package-private
   * in javax.swing. <code>TransferHandler</code> will detect text components
   * and call this method instead via reflection. It's name should therefore
   * not be changed.
   *
   * @param p the point to calculate a drop location for
   * @return the drop location, or <code>null</code>
   */
  DropLocation dropLocationForPoint(Point p) {
    Position.Bias[] bias = new Position.Bias[1];
    int index = getUI().viewToModel(this, p, bias);

    // viewToModel currently returns null for some HTML content
    // when the point is within the component's top inset
    if (bias[0] == null) {
      bias[0] = Position.Bias.Forward;
    }

    return new DropLocation(p, index, bias[0]);
  }

  /**
   * Called to set or clear the drop location during a DnD operation.
   * In some cases, the component may need to use it's internal selection
   * temporarily to indicate the drop location. To help facilitate this,
   * this method returns and accepts as a parameter a state object.
   * This state object can be used to store, and later restore, the selection
   * state. Whatever this method returns will be passed back to it in
   * future calls, as the state parameter. If it wants the DnD system to
   * continue storing the same state, it must pass it back every time.
   * Here's how this is used:
   * <p>
   * Let's say that on the first call to this method the component decides
   * to save some state (because it is about to use the selection to show
   * a drop index). It can return a state object to the caller encapsulating
   * any saved selection state. On a second call, let's say the drop location
   * is being changed to something else. The component doesn't need to
   * restore anything yet, so it simply passes back the same state object
   * to have the DnD system continue storing it. Finally, let's say this
   * method is messaged with <code>null</code>. This means DnD
   * is finished with this component for now, meaning it should restore
   * state. At this point, it can use the state parameter to restore
   * said state, and of course return <code>null</code> since there's
   * no longer anything to store.
   * <p>
   * Note: This method is meant to override
   * <code>JComponent.setDropLocation()</code>, which is package-private
   * in javax.swing. <code>TransferHandler</code> will detect text components
   * and call this method instead via reflection. It's name should therefore
   * not be changed.
   *
   * @param location the drop location (as calculated by <code>dropLocationForPoint</code>) or
   * <code>null</code> if there's no longer a valid drop location
   * @param state the state object saved earlier for this component, or <code>null</code>
   * @param forDrop whether or not the method is being called because an actual drop occurred
   * @return any saved state for this component, or <code>null</code> if none
   */
  Object setDropLocation(TransferHandler.DropLocation location,
      Object state,
      boolean forDrop) {

    Object retVal = null;
    DropLocation textLocation = (DropLocation) location;

    if (dropMode == DropMode.USE_SELECTION) {
      if (textLocation == null) {
        if (state != null) {
                    /*
                     * This object represents the state saved earlier.
                     *     If the caret is a DefaultCaret it will be
                     *     an Object array containing, in order:
                     *         - the saved caret mark (Integer)
                     *         - the saved caret dot (Integer)
                     *         - the saved caret visibility (Boolean)
                     *         - the saved mark bias (Position.Bias)
                     *         - the saved dot bias (Position.Bias)
                     *     If the caret is not a DefaultCaret it will
                     *     be similar, but will not contain the dot
                     *     or mark bias.
                     */
          Object[] vals = (Object[]) state;

          if (!forDrop) {
            if (caret instanceof DefaultCaret) {
              ((DefaultCaret) caret).setDot(((Integer) vals[0]).intValue(),
                  (Position.Bias) vals[3]);
              ((DefaultCaret) caret).moveDot(((Integer) vals[1]).intValue(),
                  (Position.Bias) vals[4]);
            } else {
              caret.setDot(((Integer) vals[0]).intValue());
              caret.moveDot(((Integer) vals[1]).intValue());
            }
          }

          caret.setVisible(((Boolean) vals[2]).booleanValue());
        }
      } else {
        if (dropLocation == null) {
          boolean visible;

          if (caret instanceof DefaultCaret) {
            DefaultCaret dc = (DefaultCaret) caret;
            visible = dc.isActive();
            retVal = new Object[]{Integer.valueOf(dc.getMark()),
                Integer.valueOf(dc.getDot()),
                Boolean.valueOf(visible),
                dc.getMarkBias(),
                dc.getDotBias()};
          } else {
            visible = caret.isVisible();
            retVal = new Object[]{Integer.valueOf(caret.getMark()),
                Integer.valueOf(caret.getDot()),
                Boolean.valueOf(visible)};
          }

          caret.setVisible(true);
        } else {
          retVal = state;
        }

        if (caret instanceof DefaultCaret) {
          ((DefaultCaret) caret).setDot(textLocation.getIndex(), textLocation.getBias());
        } else {
          caret.setDot(textLocation.getIndex());
        }
      }
    } else {
      if (textLocation == null) {
        if (state != null) {
          caret.setVisible(((Boolean) state).booleanValue());
        }
      } else {
        if (dropLocation == null) {
          boolean visible = caret instanceof DefaultCaret
              ? ((DefaultCaret) caret).isActive()
              : caret.isVisible();
          retVal = Boolean.valueOf(visible);
          caret.setVisible(false);
        } else {
          retVal = state;
        }
      }
    }

    DropLocation old = dropLocation;
    dropLocation = textLocation;
    firePropertyChange("dropLocation", old, dropLocation);

    return retVal;
  }

  /**
   * Returns the location that this component should visually indicate
   * as the drop location during a DnD operation over the component,
   * or {@code null} if no location is to currently be shown.
   * <p>
   * This method is not meant for querying the drop location
   * from a {@code TransferHandler}, as the drop location is only
   * set after the {@code TransferHandler}'s <code>canImport</code>
   * has returned and has allowed for the location to be shown.
   * <p>
   * When this property changes, a property change event with
   * name "dropLocation" is fired by the component.
   *
   * @return the drop location
   * @see #setDropMode
   * @see TransferHandler#canImport(TransferHandler.TransferSupport)
   * @since 1.6
   */
  public final DropLocation getDropLocation() {
    return dropLocation;
  }


  /**
   * Updates the <code>InputMap</code>s in response to a
   * <code>Keymap</code> change.
   *
   * @param oldKm the old <code>Keymap</code>
   * @param newKm the new <code>Keymap</code>
   */
  void updateInputMap(Keymap oldKm, Keymap newKm) {
    // Locate the current KeymapWrapper.
    InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
    InputMap last = km;
    while (km != null && !(km instanceof KeymapWrapper)) {
      last = km;
      km = km.getParent();
    }
    if (km != null) {
      // Found it, tweak the InputMap that points to it, as well
      // as anything it points to.
      if (newKm == null) {
        if (last != km) {
          last.setParent(km.getParent());
        } else {
          last.setParent(null);
        }
      } else {
        InputMap newKM = new KeymapWrapper(newKm);
        last.setParent(newKM);
        if (last != km) {
          newKM.setParent(km.getParent());
        }
      }
    } else if (newKm != null) {
      km = getInputMap(JComponent.WHEN_FOCUSED);
      if (km != null) {
        // Couldn't find it.
        // Set the parent of WHEN_FOCUSED InputMap to be the new one.
        InputMap newKM = new KeymapWrapper(newKm);
        newKM.setParent(km.getParent());
        km.setParent(newKM);
      }
    }

    // Do the same thing with the ActionMap
    ActionMap am = getActionMap();
    ActionMap lastAM = am;
    while (am != null && !(am instanceof KeymapActionMap)) {
      lastAM = am;
      am = am.getParent();
    }
    if (am != null) {
      // Found it, tweak the Actionap that points to it, as well
      // as anything it points to.
      if (newKm == null) {
        if (lastAM != am) {
          lastAM.setParent(am.getParent());
        } else {
          lastAM.setParent(null);
        }
      } else {
        ActionMap newAM = new KeymapActionMap(newKm);
        lastAM.setParent(newAM);
        if (lastAM != am) {
          newAM.setParent(am.getParent());
        }
      }
    } else if (newKm != null) {
      am = getActionMap();
      if (am != null) {
        // Couldn't find it.
        // Set the parent of ActionMap to be the new one.
        ActionMap newAM = new KeymapActionMap(newKm);
        newAM.setParent(am.getParent());
        am.setParent(newAM);
      }
    }
  }

  /**
   * Fetches the keymap currently active in this text
   * component.
   *
   * @return the keymap
   */
  public Keymap getKeymap() {
    return keymap;
  }

  /**
   * Adds a new keymap into the keymap hierarchy.  Keymap bindings
   * resolve from bottom up so an attribute specified in a child
   * will override an attribute specified in the parent.
   *
   * @param nm the name of the keymap (must be unique within the collection of named keymaps in the
   * document); the name may be <code>null</code> if the keymap is unnamed, but the caller is
   * responsible for managing the reference returned as an unnamed keymap can't be fetched by name
   * @param parent the parent keymap; this may be <code>null</code> if unspecified bindings need not
   * be resolved in some other keymap
   * @return the keymap
   */
  public static Keymap addKeymap(String nm, Keymap parent) {
    Keymap map = new DefaultKeymap(nm, parent);
    if (nm != null) {
      // add a named keymap, a class of bindings
      getKeymapTable().put(nm, map);
    }
    return map;
  }

  /**
   * Removes a named keymap previously added to the document.  Keymaps
   * with <code>null</code> names may not be removed in this way.
   *
   * @param nm the name of the keymap to remove
   * @return the keymap that was removed
   */
  public static Keymap removeKeymap(String nm) {
    return getKeymapTable().remove(nm);
  }

  /**
   * Fetches a named keymap previously added to the document.
   * This does not work with <code>null</code>-named keymaps.
   *
   * @param nm the name of the keymap
   * @return the keymap
   */
  public static Keymap getKeymap(String nm) {
    return getKeymapTable().get(nm);
  }

  private static HashMap<String, Keymap> getKeymapTable() {
    synchronized (KEYMAP_TABLE) {
      AppContext appContext = AppContext.getAppContext();
      HashMap<String, Keymap> keymapTable =
          (HashMap<String, Keymap>) appContext.get(KEYMAP_TABLE);
      if (keymapTable == null) {
        keymapTable = new HashMap<String, Keymap>(17);
        appContext.put(KEYMAP_TABLE, keymapTable);
        //initialize default keymap
        Keymap binding = addKeymap(DEFAULT_KEYMAP, null);
        binding.setDefaultAction(new
            DefaultEditorKit.DefaultKeyTypedAction());
      }
      return keymapTable;
    }
  }

  /**
   * Binding record for creating key bindings.
   * <p>
   * <strong>Warning:</strong>
   * Serialized objects of this class will not be compatible with
   * future Swing releases. The current serialization support is
   * appropriate for short term storage or RMI between applications running
   * the same version of Swing.  As of 1.4, support for long term storage
   * of all JavaBeans&trade;
   * has been added to the <code>java.beans</code> package.
   * Please see {@link java.beans.XMLEncoder}.
   */
  public static class KeyBinding {

    /**
     * The key.
     */
    public KeyStroke key;

    /**
     * The name of the action for the key.
     */
    public String actionName;

    /**
     * Creates a new key binding.
     *
     * @param key the key
     * @param actionName the name of the action for the key
     */
    public KeyBinding(KeyStroke key, String actionName) {
      this.key = key;
      this.actionName = actionName;
    }
  }

  /**
   * <p>
   * Loads a keymap with a bunch of
   * bindings.  This can be used to take a static table of
   * definitions and load them into some keymap.  The following
   * example illustrates an example of binding some keys to
   * the cut, copy, and paste actions associated with a
   * JTextComponent.  A code fragment to accomplish
   * this might look as follows:
   * <pre><code>
   *
   *   static final JTextComponent.KeyBinding[] defaultBindings = {
   *     new JTextComponent.KeyBinding(
   *       KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
   *       DefaultEditorKit.copyAction),
   *     new JTextComponent.KeyBinding(
   *       KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
   *       DefaultEditorKit.pasteAction),
   *     new JTextComponent.KeyBinding(
   *       KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK),
   *       DefaultEditorKit.cutAction),
   *   };
   *
   *   JTextComponent c = new JTextPane();
   *   Keymap k = c.getKeymap();
   *   JTextComponent.loadKeymap(k, defaultBindings, c.getActions());
   *
   * </code></pre>
   * The sets of bindings and actions may be empty but must be
   * non-<code>null</code>.
   *
   * @param map the keymap
   * @param bindings the bindings
   * @param actions the set of actions
   */
  public static void loadKeymap(Keymap map, KeyBinding[] bindings, Action[] actions) {
    Hashtable<String, Action> h = new Hashtable<String, Action>();
    for (Action a : actions) {
      String value = (String) a.getValue(Action.NAME);
      h.put((value != null ? value : ""), a);
    }
    for (KeyBinding binding : bindings) {
      Action a = h.get(binding.actionName);
      if (a != null) {
        map.addActionForKeyStroke(binding.key, a);
      }
    }
  }

  /**
   * Fetches the current color used to render the
   * caret.
   *
   * @return the color
   */
  public Color getCaretColor() {
    return caretColor;
  }

  /**
   * Sets the current color used to render the caret.
   * Setting to <code>null</code> effectively restores the default color.
   * Setting the color results in a PropertyChange event ("caretColor")
   * being fired.
   *
   * @param c the color
   * @beaninfo description: the color used to render the caret bound: true preferred: true
   * @see #getCaretColor
   */
  public void setCaretColor(Color c) {
    Color old = caretColor;
    caretColor = c;
    firePropertyChange("caretColor", old, caretColor);
  }

  /**
   * Fetches the current color used to render the
   * selection.
   *
   * @return the color
   */
  public Color getSelectionColor() {
    return selectionColor;
  }

  /**
   * Sets the current color used to render the selection.
   * Setting the color to <code>null</code> is the same as setting
   * <code>Color.white</code>.  Setting the color results in a
   * PropertyChange event ("selectionColor").
   *
   * @param c the color
   * @beaninfo description: color used to render selection background bound: true preferred: true
   * @see #getSelectionColor
   */
  public void setSelectionColor(Color c) {
    Color old = selectionColor;
    selectionColor = c;
    firePropertyChange("selectionColor", old, selectionColor);
  }

  /**
   * Fetches the current color used to render the
   * selected text.
   *
   * @return the color
   */
  public Color getSelectedTextColor() {
    return selectedTextColor;
  }

  /**
   * Sets the current color used to render the selected text.
   * Setting the color to <code>null</code> is the same as
   * <code>Color.black</code>. Setting the color results in a
   * PropertyChange event ("selectedTextColor") being fired.
   *
   * @param c the color
   * @beaninfo description: color used to render selected text bound: true preferred: true
   * @see #getSelectedTextColor
   */
  public void setSelectedTextColor(Color c) {
    Color old = selectedTextColor;
    selectedTextColor = c;
    firePropertyChange("selectedTextColor", old, selectedTextColor);
  }

  /**
   * Fetches the current color used to render the
   * disabled text.
   *
   * @return the color
   */
  public Color getDisabledTextColor() {
    return disabledTextColor;
  }

  /**
   * Sets the current color used to render the
   * disabled text.  Setting the color fires off a
   * PropertyChange event ("disabledTextColor").
   *
   * @param c the color
   * @beaninfo description: color used to render disabled text bound: true preferred: true
   * @see #getDisabledTextColor
   */
  public void setDisabledTextColor(Color c) {
    Color old = disabledTextColor;
    disabledTextColor = c;
    firePropertyChange("disabledTextColor", old, disabledTextColor);
  }

  /**
   * Replaces the currently selected content with new content
   * represented by the given string.  If there is no selection
   * this amounts to an insert of the given text.  If there
   * is no replacement text this amounts to a removal of the
   * current selection.
   * <p>
   * This is the method that is used by the default implementation
   * of the action for inserting content that gets bound to the
   * keymap actions.
   *
   * @param content the content to replace the selection with
   */
  public void replaceSelection(String content) {
    Document doc = getDocument();
    if (doc != null) {
      try {
        boolean composedTextSaved = saveComposedText(caret.getDot());
        int p0 = Math.min(caret.getDot(), caret.getMark());
        int p1 = Math.max(caret.getDot(), caret.getMark());
        if (doc instanceof AbstractDocument) {
          ((AbstractDocument) doc).replace(p0, p1 - p0, content, null);
        } else {
          if (p0 != p1) {
            doc.remove(p0, p1 - p0);
          }
          if (content != null && content.length() > 0) {
            doc.insertString(p0, content, null);
          }
        }
        if (composedTextSaved) {
          restoreComposedText();
        }
      } catch (BadLocationException e) {
        UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
      }
    }
  }

  /**
   * Fetches a portion of the text represented by the
   * component.  Returns an empty string if length is 0.
   *
   * @param offs the offset &ge; 0
   * @param len the length &ge; 0
   * @return the text
   * @throws BadLocationException if the offset or length are invalid
   */
  public String getText(int offs, int len) throws BadLocationException {
    return getDocument().getText(offs, len);
  }

  /**
   * Converts the given location in the model to a place in
   * the view coordinate system.
   * The component must have a positive size for
   * this translation to be computed (i.e. layout cannot
   * be computed until the component has been sized).  The
   * component does not have to be visible or painted.
   *
   * @param pos the position &ge; 0
   * @return the coordinates as a rectangle, with (r.x, r.y) as the location in the coordinate
   * system, or null if the component does not yet have a positive size.
   * @throws BadLocationException if the given position does not represent a valid location in the
   * associated document
   * @see TextUI#modelToView
   */
  public Rectangle modelToView(int pos) throws BadLocationException {
    return getUI().modelToView(this, pos);
  }

  /**
   * Converts the given place in the view coordinate system
   * to the nearest representative location in the model.
   * The component must have a positive size for
   * this translation to be computed (i.e. layout cannot
   * be computed until the component has been sized).  The
   * component does not have to be visible or painted.
   *
   * @param pt the location in the view to translate
   * @return the offset &ge; 0 from the start of the document, or -1 if the component does not yet
   * have a positive size.
   * @see TextUI#viewToModel
   */
  public int viewToModel(Point pt) {
    return getUI().viewToModel(this, pt);
  }

  /**
   * Transfers the currently selected range in the associated
   * text model to the system clipboard, removing the contents
   * from the model.  The current selection is reset.  Does nothing
   * for <code>null</code> selections.
   *
   * @see java.awt.Toolkit#getSystemClipboard
   * @see java.awt.datatransfer.Clipboard
   */
  public void cut() {
    if (isEditable() && isEnabled()) {
      invokeAction("cut", TransferHandler.getCutAction());
    }
  }

  /**
   * Transfers the currently selected range in the associated
   * text model to the system clipboard, leaving the contents
   * in the text model.  The current selection remains intact.
   * Does nothing for <code>null</code> selections.
   *
   * @see java.awt.Toolkit#getSystemClipboard
   * @see java.awt.datatransfer.Clipboard
   */
  public void copy() {
    invokeAction("copy", TransferHandler.getCopyAction());
  }

  /**
   * Transfers the contents of the system clipboard into the
   * associated text model.  If there is a selection in the
   * associated view, it is replaced with the contents of the
   * clipboard.  If there is no selection, the clipboard contents
   * are inserted in front of the current insert position in
   * the associated view.  If the clipboard is empty, does nothing.
   *
   * @see #replaceSelection
   * @see java.awt.Toolkit#getSystemClipboard
   * @see java.awt.datatransfer.Clipboard
   */
  public void paste() {
    if (isEditable() && isEnabled()) {
      invokeAction("paste", TransferHandler.getPasteAction());
    }
  }

  /**
   * This is a convenience method that is only useful for
   * <code>cut</code>, <code>copy</code> and <code>paste</code>.  If
   * an <code>Action</code> with the name <code>name</code> does not
   * exist in the <code>ActionMap</code>, this will attempt to install a
   * <code>TransferHandler</code> and then use <code>altAction</code>.
   */
  private void invokeAction(String name, Action altAction) {
    ActionMap map = getActionMap();
    Action action = null;

    if (map != null) {
      action = map.get(name);
    }
    if (action == null) {
      installDefaultTransferHandlerIfNecessary();
      action = altAction;
    }
    action.actionPerformed(new ActionEvent(this,
        ActionEvent.ACTION_PERFORMED, (String) action.
        getValue(Action.NAME),
        EventQueue.getMostRecentEventTime(),
        getCurrentEventModifiers()));
  }

  /**
   * If the current <code>TransferHandler</code> is null, this will
   * install a new one.
   */
  private void installDefaultTransferHandlerIfNecessary() {
    if (getTransferHandler() == null) {
      if (defaultTransferHandler == null) {
        defaultTransferHandler = new DefaultTransferHandler();
      }
      setTransferHandler(defaultTransferHandler);
    }
  }

  /**
   * Moves the caret to a new position, leaving behind a mark
   * defined by the last time <code>setCaretPosition</code> was
   * called.  This forms a selection.
   * If the document is <code>null</code>, does nothing. The position
   * must be between 0 and the length of the component's text or else
   * an exception is thrown.
   *
   * @param pos the position
   * @throws IllegalArgumentException if the value supplied for <code>position</code> is less than
   * zero or greater than the component's text length
   * @see #setCaretPosition
   */
  public void moveCaretPosition(int pos) {
    Document doc = getDocument();
    if (doc != null) {
      if (pos > doc.getLength() || pos < 0) {
        throw new IllegalArgumentException("bad position: " + pos);
      }
      caret.moveDot(pos);
    }
  }

  /**
   * The bound property name for the focus accelerator.
   */
  public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey";

  /**
   * Sets the key accelerator that will cause the receiving text
   * component to get the focus.  The accelerator will be the
   * key combination of the platform-specific modifier key and
   * the character given (converted to upper case).  For example,
   * the ALT key is used as a modifier on Windows and the CTRL+ALT
   * combination is used on Mac.  By default, there is no focus
   * accelerator key.  Any previous key accelerator setting will be
   * superseded.  A '\0' key setting will be registered, and has the
   * effect of turning off the focus accelerator.  When the new key
   * is set, a PropertyChange event (FOCUS_ACCELERATOR_KEY) will be fired.
   *
   * @param aKey the key
   * @beaninfo description: accelerator character used to grab focus bound: true
   * @see #getFocusAccelerator
   */
  public void setFocusAccelerator(char aKey) {
    aKey = Character.toUpperCase(aKey);
    char old = focusAccelerator;
    focusAccelerator = aKey;
    // Fix for 4341002: value of FOCUS_ACCELERATOR_KEY is wrong.
    // So we fire both FOCUS_ACCELERATOR_KEY, for compatibility,
    // and the correct event here.
    firePropertyChange(FOCUS_ACCELERATOR_KEY, old, focusAccelerator);
    firePropertyChange("focusAccelerator", old, focusAccelerator);
  }

  /**
   * Returns the key accelerator that will cause the receiving
   * text component to get the focus.  Return '\0' if no focus
   * accelerator has been set.
   *
   * @return the key
   */
  public char getFocusAccelerator() {
    return focusAccelerator;
  }

  /**
   * Initializes from a stream.  This creates a
   * model of the type appropriate for the component
   * and initializes the model from the stream.
   * By default this will load the model as plain
   * text.  Previous contents of the model are discarded.
   *
   * @param in the stream to read from
   * @param desc an object describing the stream; this might be a string, a File, a URL, etc.  Some
   * kinds of documents (such as html for example) might be able to make use of this information; if
   * non-<code>null</code>, it is added as a property of the document
   * @throws IOException as thrown by the stream being used to initialize
   * @see EditorKit#createDefaultDocument
   * @see #setDocument
   * @see PlainDocument
   */
  public void read(Reader in, Object desc) throws IOException {
    EditorKit kit = getUI().getEditorKit(this);
    Document doc = kit.createDefaultDocument();
    if (desc != null) {
      doc.putProperty(Document.StreamDescriptionProperty, desc);
    }
    try {
      kit.read(in, doc, 0);
      setDocument(doc);
    } catch (BadLocationException e) {
      throw new IOException(e.getMessage());
    }
  }

  /**
   * Stores the contents of the model into the given
   * stream.  By default this will store the model as plain
   * text.
   *
   * @param out the output stream
   * @throws IOException on any I/O error
   */
  public void write(Writer out) throws IOException {
    Document doc = getDocument();
    try {
      getUI().getEditorKit(this).write(out, doc, 0, doc.getLength());
    } catch (BadLocationException e) {
      throw new IOException(e.getMessage());
    }
  }

  public void removeNotify() {
    super.removeNotify();
    if (getFocusedComponent() == this) {
      AppContext.getAppContext().remove(FOCUSED_COMPONENT);
    }
  }

  // --- java.awt.TextComponent methods ------------------------

  /**
   * Sets the position of the text insertion caret for the
   * <code>TextComponent</code>.  Note that the caret tracks change,
   * so this may move if the underlying text of the component is changed.
   * If the document is <code>null</code>, does nothing. The position
   * must be between 0 and the length of the component's text or else
   * an exception is thrown.
   *
   * @param position the position
   * @throws IllegalArgumentException if the value supplied for <code>position</code> is less than
   * zero or greater than the component's text length
   * @beaninfo description: the caret position
   */
  public void setCaretPosition(int position) {
    Document doc = getDocument();
    if (doc != null) {
      if (position > doc.getLength() || position < 0) {
        throw new IllegalArgumentException("bad position: " + position);
      }
      caret.setDot(position);
    }
  }

  /**
   * Returns the position of the text insertion caret for the
   * text component.
   *
   * @return the position of the text insertion caret for the text component &ge; 0
   */
  @Transient
  public int getCaretPosition() {
    return caret.getDot();
  }

  /**
   * Sets the text of this <code>TextComponent</code>
   * to the specified text.  If the text is <code>null</code>
   * or empty, has the effect of simply deleting the old text.
   * When text has been inserted, the resulting caret location
   * is determined by the implementation of the caret class.
   *
   * <p>
   * Note that text is not a bound property, so no <code>PropertyChangeEvent
   * </code> is fired when it changes. To listen for changes to the text,
   * use <code>DocumentListener</code>.
   *
   * @param t the new text to be set
   * @beaninfo description: the text of this component
   * @see #getText
   * @see DefaultCaret
   */
  public void setText(String t) {
    try {
      Document doc = getDocument();
      if (doc instanceof AbstractDocument) {
        ((AbstractDocument) doc).replace(0, doc.getLength(), t, null);
      } else {
        doc.remove(0, doc.getLength());
        doc.insertString(0, t, null);
      }
    } catch (BadLocationException e) {
      UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
    }
  }

  /**
   * Returns the text contained in this <code>TextComponent</code>.
   * If the underlying document is <code>null</code>,
   * will give a <code>NullPointerException</code>.
   *
   * Note that text is not a bound property, so no <code>PropertyChangeEvent
   * </code> is fired when it changes. To listen for changes to the text,
   * use <code>DocumentListener</code>.
   *
   * @return the text
   * @throws NullPointerException if the document is <code>null</code>
   * @see #setText
   */
  public String getText() {
    Document doc = getDocument();
    String txt;
    try {
      txt = doc.getText(0, doc.getLength());
    } catch (BadLocationException e) {
      txt = null;
    }
    return txt;
  }

  /**
   * Returns the selected text contained in this
   * <code>TextComponent</code>.  If the selection is
   * <code>null</code> or the document empty, returns <code>null</code>.
   *
   * @return the text
   * @throws IllegalArgumentException if the selection doesn't have a valid mapping into the
   * document for some reason
   * @see #setText
   */
  public String getSelectedText() {
    String txt = null;
    int p0 = Math.min(caret.getDot(), caret.getMark());
    int p1 = Math.max(caret.getDot(), caret.getMark());
    if (p0 != p1) {
      try {
        Document doc = getDocument();
        txt = doc.getText(p0, p1 - p0);
      } catch (BadLocationException e) {
        throw new IllegalArgumentException(e.getMessage());
      }
    }
    return txt;
  }

  /**
   * Returns the boolean indicating whether this
   * <code>TextComponent</code> is editable or not.
   *
   * @return the boolean value
   * @see #setEditable
   */
  public boolean isEditable() {
    return editable;
  }

  /**
   * Sets the specified boolean to indicate whether or not this
   * <code>TextComponent</code> should be editable.
   * A PropertyChange event ("editable") is fired when the
   * state is changed.
   *
   * @param b the boolean to be set
   * @beaninfo description: specifies if the text can be edited bound: true
   * @see #isEditable
   */
  public void setEditable(boolean b) {
    if (b != editable) {
      boolean oldVal = editable;
      editable = b;
      enableInputMethods(editable);
      firePropertyChange("editable", Boolean.valueOf(oldVal), Boolean.valueOf(editable));
      repaint();
    }
  }

  /**
   * Returns the selected text's start position.  Return 0 for an
   * empty document, or the value of dot if no selection.
   *
   * @return the start position &ge; 0
   */
  @Transient
  public int getSelectionStart() {
    int start = Math.min(caret.getDot(), caret.getMark());
    return start;
  }

  /**
   * Sets the selection start to the specified position.  The new
   * starting point is constrained to be before or at the current
   * selection end.
   * <p>
   * This is available for backward compatibility to code
   * that called this method on <code>java.awt.TextComponent</code>.
   * This is implemented to forward to the <code>Caret</code>
   * implementation which is where the actual selection is maintained.
   *
   * @param selectionStart the start position of the text &ge; 0
   * @beaninfo description: starting location of the selection.
   */
  public void setSelectionStart(int selectionStart) {
        /* Route through select method to enforce consistent policy
         * between selectionStart and selectionEnd.
         */
    select(selectionStart, getSelectionEnd());
  }

  /**
   * Returns the selected text's end position.  Return 0 if the document
   * is empty, or the value of dot if there is no selection.
   *
   * @return the end position &ge; 0
   */
  @Transient
  public int getSelectionEnd() {
    int end = Math.max(caret.getDot(), caret.getMark());
    return end;
  }

  /**
   * Sets the selection end to the specified position.  The new
   * end point is constrained to be at or after the current
   * selection start.
   * <p>
   * This is available for backward compatibility to code
   * that called this method on <code>java.awt.TextComponent</code>.
   * This is implemented to forward to the <code>Caret</code>
   * implementation which is where the actual selection is maintained.
   *
   * @param selectionEnd the end position of the text &ge; 0
   * @beaninfo description: ending location of the selection.
   */
  public void setSelectionEnd(int selectionEnd) {
        /* Route through select method to enforce consistent policy
         * between selectionStart and selectionEnd.
         */
    select(getSelectionStart(), selectionEnd);
  }

  /**
   * Selects the text between the specified start and end positions.
   * <p>
   * This method sets the start and end positions of the
   * selected text, enforcing the restriction that the start position
   * must be greater than or equal to zero.  The end position must be
   * greater than or equal to the start position, and less than or
   * equal to the length of the text component's text.
   * <p>
   * If the caller supplies values that are inconsistent or out of
   * bounds, the method enforces these constraints silently, and
   * without failure. Specifically, if the start position or end
   * position is greater than the length of the text, it is reset to
   * equal the text length. If the start position is less than zero,
   * it is reset to zero, and if the end position is less than the
   * start position, it is reset to the start position.
   * <p>
   * This call is provided for backward compatibility.
   * It is routed to a call to <code>setCaretPosition</code>
   * followed by a call to <code>moveCaretPosition</code>.
   * The preferred way to manage selection is by calling
   * those methods directly.
   *
   * @param selectionStart the start position of the text
   * @param selectionEnd the end position of the text
   * @see #setCaretPosition
   * @see #moveCaretPosition
   */
  public void select(int selectionStart, int selectionEnd) {
    // argument adjustment done by java.awt.TextComponent
    int docLength = getDocument().getLength();

    if (selectionStart < 0) {
      selectionStart = 0;
    }
    if (selectionStart > docLength) {
      selectionStart = docLength;
    }
    if (selectionEnd > docLength) {
      selectionEnd = docLength;
    }
    if (selectionEnd < selectionStart) {
      selectionEnd = selectionStart;
    }

    setCaretPosition(selectionStart);
    moveCaretPosition(selectionEnd);
  }

  /**
   * Selects all the text in the <code>TextComponent</code>.
   * Does nothing on a <code>null</code> or empty document.
   */
  public void selectAll() {
    Document doc = getDocument();
    if (doc != null) {
      setCaretPosition(0);
      moveCaretPosition(doc.getLength());
    }
  }

  // --- Tooltip Methods ---------------------------------------------

  /**
   * Returns the string to be used as the tooltip for <code>event</code>.
   * This will return one of:
   * <ol>
   * <li>If <code>setToolTipText</code> has been invoked with a
   * non-<code>null</code>
   * value, it will be returned, otherwise
   * <li>The value from invoking <code>getToolTipText</code> on
   * the UI will be returned.
   * </ol>
   * By default <code>JTextComponent</code> does not register
   * itself with the <code>ToolTipManager</code>.
   * This means that tooltips will NOT be shown from the
   * <code>TextUI</code> unless <code>registerComponent</code> has
   * been invoked on the <code>ToolTipManager</code>.
   *
   * @param event the event in question
   * @return the string to be used as the tooltip for <code>event</code>
   * @see javax.swing.JComponent#setToolTipText
   * @see javax.swing.plaf.TextUI#getToolTipText
   * @see javax.swing.ToolTipManager#registerComponent
   */
  public String getToolTipText(MouseEvent event) {
    String retValue = super.getToolTipText(event);

    if (retValue == null) {
      TextUI ui = getUI();
      if (ui != null) {
        retValue = ui.getToolTipText(this, new Point(event.getX(),
            event.getY()));
      }
    }
    return retValue;
  }

  // --- Scrollable methods ---------------------------------------------

  /**
   * Returns the preferred size of the viewport for a view component.
   * This is implemented to do the default behavior of returning
   * the preferred size of the component.
   *
   * @return the <code>preferredSize</code> of a <code>JViewport</code> whose view is this
   * <code>Scrollable</code>
   */
  public Dimension getPreferredScrollableViewportSize() {
    return getPreferredSize();
  }


  /**
   * Components that display logical rows or columns should compute
   * the scroll increment that will completely expose one new row
   * or column, depending on the value of orientation.  Ideally,
   * components should handle a partially exposed row or column by
   * returning the distance required to completely expose the item.
   * <p>
   * The default implementation of this is to simply return 10% of
   * the visible area.  Subclasses are likely to be able to provide
   * a much more reasonable value.
   *
   * @param visibleRect the view area visible within the viewport
   * @param orientation either <code>SwingConstants.VERTICAL</code> or
   * <code>SwingConstants.HORIZONTAL</code>
   * @param direction less than zero to scroll up/left, greater than zero for down/right
   * @return the "unit" increment for scrolling in the specified direction
   * @throws IllegalArgumentException for an invalid orientation
   * @see JScrollBar#setUnitIncrement
   */
  public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
    switch (orientation) {
      case SwingConstants.VERTICAL:
        return visibleRect.height / 10;
      case SwingConstants.HORIZONTAL:
        return visibleRect.width / 10;
      default:
        throw new IllegalArgumentException("Invalid orientation: " + orientation);
    }
  }


  /**
   * Components that display logical rows or columns should compute
   * the scroll increment that will completely expose one block
   * of rows or columns, depending on the value of orientation.
   * <p>
   * The default implementation of this is to simply return the visible
   * area.  Subclasses will likely be able to provide a much more
   * reasonable value.
   *
   * @param visibleRect the view area visible within the viewport
   * @param orientation either <code>SwingConstants.VERTICAL</code> or
   * <code>SwingConstants.HORIZONTAL</code>
   * @param direction less than zero to scroll up/left, greater than zero for down/right
   * @return the "block" increment for scrolling in the specified direction
   * @throws IllegalArgumentException for an invalid orientation
   * @see JScrollBar#setBlockIncrement
   */
  public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
    switch (orientation) {
      case SwingConstants.VERTICAL:
        return visibleRect.height;
      case SwingConstants.HORIZONTAL:
        return visibleRect.width;
      default:
        throw new IllegalArgumentException("Invalid orientation: " + orientation);
    }
  }


  /**
   * Returns true if a viewport should always force the width of this
   * <code>Scrollable</code> to match the width of the viewport.
   * For example a normal text view that supported line wrapping
   * would return true here, since it would be undesirable for
   * wrapped lines to disappear beyond the right
   * edge of the viewport.  Note that returning true for a
   * <code>Scrollable</code> whose ancestor is a <code>JScrollPane</code>
   * effectively disables horizontal scrolling.
   * <p>
   * Scrolling containers, like <code>JViewport</code>,
   * will use this method each time they are validated.
   *
   * @return true if a viewport should force the <code>Scrollable</code>s width to match its own
   */
  public boolean getScrollableTracksViewportWidth() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    if (parent instanceof JViewport) {
      return parent.getWidth() > getPreferredSize().width;
    }
    return false;
  }

  /**
   * Returns true if a viewport should always force the height of this
   * <code>Scrollable</code> to match the height of the viewport.
   * For example a columnar text view that flowed text in left to
   * right columns could effectively disable vertical scrolling by
   * returning true here.
   * <p>
   * Scrolling containers, like <code>JViewport</code>,
   * will use this method each time they are validated.
   *
   * @return true if a viewport should force the Scrollables height to match its own
   */
  public boolean getScrollableTracksViewportHeight() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    if (parent instanceof JViewport) {
      return parent.getHeight() > getPreferredSize().height;
    }
    return false;
  }

//////////////////
// Printing Support
//////////////////

  /**
   * A convenience print method that displays a print dialog, and then prints this {@code
   * JTextComponent} in <i>interactive</i> mode with no header or footer text. Note: this method
   * blocks until printing is done. <p> Note: In <i>headless</i> mode, no dialogs will be shown.
   *
   * <p> This method calls the full featured {@link #print(MessageFormat, MessageFormat, boolean,
   * PrintService, PrintRequestAttributeSet, boolean) print} method to perform printing.
   *
   * @return {@code true}, unless printing is canceled by the user
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet,
   * boolean)
   * @since 1.6
   */

  public boolean print() throws PrinterException {
    return print(null, null, true, null, null, true);
  }

  /**
   * A convenience print method that displays a print dialog, and then prints this {@code
   * JTextComponent} in <i>interactive</i> mode with the specified header and footer text. Note:
   * this method blocks until printing is done. <p> Note: In <i>headless</i> mode, no dialogs will
   * be shown.
   *
   * <p> This method calls the full featured {@link #print(MessageFormat, MessageFormat, boolean,
   * PrintService, PrintRequestAttributeSet, boolean) print} method to perform printing.
   *
   * @param headerFormat the text, in {@code MessageFormat}, to be used as the header, or {@code
   * null} for no header
   * @param footerFormat the text, in {@code MessageFormat}, to be used as the footer, or {@code
   * null} for no footer
   * @return {@code true}, unless printing is canceled by the user
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet,
   * boolean)
   * @see java.text.MessageFormat
   * @since 1.6
   */
  public boolean print(final MessageFormat headerFormat,
      final MessageFormat footerFormat) throws PrinterException {
    return print(headerFormat, footerFormat, true, null, null, true);
  }

  /**
   * Prints the content of this {@code JTextComponent}. Note: this method
   * blocks until printing is done.
   *
   * <p>
   * Page header and footer text can be added to the output by providing
   * {@code MessageFormat} arguments. The printing code requests
   * {@code Strings} from the formats, providing a single item which may be
   * included in the formatted string: an {@code Integer} representing the
   * current page number.
   *
   * <p>
   * {@code showPrintDialog boolean} parameter allows you to specify whether
   * a print dialog is displayed to the user. When it is, the user
   * may use the dialog to change printing attributes or even cancel the
   * print.
   *
   * <p>
   * {@code service} allows you to provide the initial
   * {@code PrintService} for the print dialog, or to specify
   * {@code PrintService} to print to when the dialog is not shown.
   *
   * <p>
   * {@code attributes} can be used to provide the
   * initial values for the print dialog, or to supply any needed
   * attributes when the dialog is not shown. {@code attributes} can
   * be used to control how the job will print, for example
   * <i>duplex</i> or <i>single-sided</i>.
   *
   * <p>
   * {@code interactive boolean} parameter allows you to specify
   * whether to perform printing in <i>interactive</i>
   * mode. If {@code true}, a progress dialog, with an abort option,
   * is displayed for the duration of printing.  This dialog is
   * <i>modal</i> when {@code print} is invoked on the <i>Event Dispatch
   * Thread</i> and <i>non-modal</i> otherwise. <b>Warning</b>:
   * calling this method on the <i>Event Dispatch Thread</i> with {@code
   * interactive false} blocks <i>all</i> events, including repaints, from
   * being processed until printing is complete. It is only
   * recommended when printing from an application with no
   * visible GUI.
   *
   * <p>
   * Note: In <i>headless</i> mode, {@code showPrintDialog} and
   * {@code interactive} parameters are ignored and no dialogs are
   * shown.
   *
   * <p>
   * This method ensures the {@code document} is not mutated during printing.
   * To indicate it visually, {@code setEnabled(false)} is set for the
   * duration of printing.
   *
   * <p>
   * This method uses {@link #getPrintable} to render document content.
   *
   * <p>
   * This method is thread-safe, although most Swing methods are not. Please
   * see <A
   * HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">
   * Concurrency in Swing</A> for more information.
   *
   * <p>
   * <b>Sample Usage</b>. This code snippet shows a cross-platform print
   * dialog and then prints the {@code JTextComponent} in <i>interactive</i> mode
   * unless the user cancels the dialog:
   *
   * <pre>
   * textComponent.print(new MessageFormat(&quot;My text component header&quot;),
   *     new MessageFormat(&quot;Footer. Page - {0}&quot;), true, null, null, true);
   * </pre>
   * <p>
   * Executing this code off the <i>Event Dispatch Thread</i>
   * performs printing on the <i>background</i>.
   * The following pattern might be used for <i>background</i>
   * printing:
   * <pre>
   *     FutureTask&lt;Boolean&gt; future =
   *         new FutureTask&lt;Boolean&gt;(
   *             new Callable&lt;Boolean&gt;() {
   *                 public Boolean call() {
   *                     return textComponent.print(.....);
   *                 }
   *             });
   *     executor.execute(future);
   * </pre>
   *
   * @param headerFormat the text, in {@code MessageFormat}, to be used as the header, or {@code
   * null} for no header
   * @param footerFormat the text, in {@code MessageFormat}, to be used as the footer, or {@code
   * null} for no footer
   * @param showPrintDialog {@code true} to display a print dialog, {@code false} otherwise
   * @param service initial {@code PrintService}, or {@code null} for the default
   * @param attributes the job attributes to be applied to the print job, or {@code null} for none
   * @param interactive whether to print in an interactive mode
   * @return {@code true}, unless printing is canceled by the user
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @see #getPrintable
   * @see java.text.MessageFormat
   * @see java.awt.GraphicsEnvironment#isHeadless
   * @see java.util.concurrent.FutureTask
   * @since 1.6
   */
  public boolean print(final MessageFormat headerFormat,
      final MessageFormat footerFormat,
      final boolean showPrintDialog,
      final PrintService service,
      final PrintRequestAttributeSet attributes,
      final boolean interactive)
      throws PrinterException {

    final PrinterJob job = PrinterJob.getPrinterJob();
    final Printable printable;
    final PrintingStatus printingStatus;
    final boolean isHeadless = GraphicsEnvironment.isHeadless();
    final boolean isEventDispatchThread =
        SwingUtilities.isEventDispatchThread();
    final Printable textPrintable = getPrintable(headerFormat, footerFormat);
    if (interactive && !isHeadless) {
      printingStatus =
          PrintingStatus.createPrintingStatus(this, job);
      printable =
          printingStatus.createNotificationPrintable(textPrintable);
    } else {
      printingStatus = null;
      printable = textPrintable;
    }

    if (service != null) {
      job.setPrintService(service);
    }

    job.setPrintable(printable);

    final PrintRequestAttributeSet attr = (attributes == null)
        ? new HashPrintRequestAttributeSet()
        : attributes;

    if (showPrintDialog && !isHeadless && !job.printDialog(attr)) {
      return false;
    }

        /*
         * there are three cases for printing:
         * 1. print non interactively (! interactive || isHeadless)
         * 2. print interactively off EDT
         * 3. print interactively on EDT
         *
         * 1 and 2 prints on the current thread (3 prints on another thread)
         * 2 and 3 deal with PrintingStatusDialog
         */
    final Callable<Object> doPrint =
        new Callable<Object>() {
          public Object call() throws Exception {
            try {
              job.print(attr);
            } finally {
              if (printingStatus != null) {
                printingStatus.dispose();
              }
            }
            return null;
          }
        };

    final FutureTask<Object> futurePrinting =
        new FutureTask<Object>(doPrint);

    final Runnable runnablePrinting =
        new Runnable() {
          public void run() {
            //disable component
            boolean wasEnabled = false;
            if (isEventDispatchThread) {
              if (isEnabled()) {
                wasEnabled = true;
                setEnabled(false);
              }
            } else {
              try {
                wasEnabled = SwingUtilities2.submit(
                    new Callable<Boolean>() {
                      public Boolean call() throws Exception {
                        boolean rv = isEnabled();
                        if (rv) {
                          setEnabled(false);
                        }
                        return rv;
                      }
                    }).get();
              } catch (InterruptedException e) {
                throw new RuntimeException(e);
              } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                if (cause instanceof Error) {
                  throw (Error) cause;
                }
                if (cause instanceof RuntimeException) {
                  throw (RuntimeException) cause;
                }
                throw new AssertionError(cause);
              }
            }

            getDocument().render(futurePrinting);

            //enable component
            if (wasEnabled) {
              if (isEventDispatchThread) {
                setEnabled(true);
              } else {
                try {
                  SwingUtilities2.submit(
                      new Runnable() {
                        public void run() {
                          setEnabled(true);
                        }
                      }, null).get();
                } catch (InterruptedException e) {
                  throw new RuntimeException(e);
                } catch (ExecutionException e) {
                  Throwable cause = e.getCause();
                  if (cause instanceof Error) {
                    throw (Error) cause;
                  }
                  if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                  }
                  throw new AssertionError(cause);
                }
              }
            }
          }
        };

    if (!interactive || isHeadless) {
      runnablePrinting.run();
    } else {
      if (isEventDispatchThread) {
        (new Thread(runnablePrinting)).start();
        printingStatus.showModal(true);
      } else {
        printingStatus.showModal(false);
        runnablePrinting.run();
      }
    }

    //the printing is done successfully or otherwise.
    //dialog is hidden if needed.
    try {
      futurePrinting.get();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      Throwable cause = e.getCause();
      if (cause instanceof PrinterAbortException) {
        if (printingStatus != null
            && printingStatus.isAborted()) {
          return false;
        } else {
          throw (PrinterAbortException) cause;
        }
      } else if (cause instanceof PrinterException) {
        throw (PrinterException) cause;
      } else if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      } else if (cause instanceof Error) {
        throw (Error) cause;
      } else {
        throw new AssertionError(cause);
      }
    }
    return true;
  }


  /**
   * Returns a {@code Printable} to use for printing the content of this
   * {@code JTextComponent}. The returned {@code Printable} prints
   * the document as it looks on the screen except being reformatted
   * to fit the paper.
   * The returned {@code Printable} can be wrapped inside another
   * {@code Printable} in order to create complex reports and
   * documents.
   *
   *
   * <p>
   * The returned {@code Printable} shares the {@code document} with this
   * {@code JTextComponent}. It is the responsibility of the developer to
   * ensure that the {@code document} is not mutated while this {@code Printable}
   * is used. Printing behavior is undefined when the {@code document} is
   * mutated during printing.
   *
   * <p>
   * Page header and footer text can be added to the output by providing
   * {@code MessageFormat} arguments. The printing code requests
   * {@code Strings} from the formats, providing a single item which may be
   * included in the formatted string: an {@code Integer} representing the
   * current page number.
   *
   * <p>
   * The returned {@code Printable} when printed, formats the
   * document content appropriately for the page size. For correct
   * line wrapping the {@code imageable width} of all pages must be the
   * same. See {@link java.awt.print.PageFormat#getImageableWidth}.
   *
   * <p>
   * This method is thread-safe, although most Swing methods are not. Please
   * see <A
   * HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">
   * Concurrency in Swing</A> for more information.
   *
   * <p>
   * The returned {@code Printable} can be printed on any thread.
   *
   * <p>
   * This implementation returned {@code Printable} performs all painting on
   * the <i>Event Dispatch Thread</i>, regardless of what thread it is
   * used on.
   *
   * @param headerFormat the text, in {@code MessageFormat}, to be used as the header, or {@code
   * null} for no header
   * @param footerFormat the text, in {@code MessageFormat}, to be used as the footer, or {@code
   * null} for no footer
   * @return a {@code Printable} for use in printing content of this {@code JTextComponent}
   * @see java.awt.print.Printable
   * @see java.awt.print.PageFormat
   * @see javax.swing.text.Document#render(java.lang.Runnable)
   * @since 1.6
   */
  public Printable getPrintable(final MessageFormat headerFormat,
      final MessageFormat footerFormat) {
    return TextComponentPrintable.getPrintable(
        this, headerFormat, footerFormat);
  }

/////////////////
// Accessibility support
////////////////


  /**
   * Gets the <code>AccessibleContext</code> associated with this
   * <code>JTextComponent</code>. For text components,
   * the <code>AccessibleContext</code> takes the form of an
   * <code>AccessibleJTextComponent</code>.
   * A new <code>AccessibleJTextComponent</code> instance
   * is created if necessary.
   *
   * @return an <code>AccessibleJTextComponent</code> that serves as the
   * <code>AccessibleContext</code> of this <code>JTextComponent</code>
   */
  public AccessibleContext getAccessibleContext() {
    if (accessibleContext == null) {
      accessibleContext = new AccessibleJTextComponent();
    }
    return accessibleContext;
  }

  /**
   * This class implements accessibility support for the
   * <code>JTextComponent</code> class.  It provides an implementation of
   * the Java Accessibility API appropriate to menu user-interface elements.
   * <p>
   * <strong>Warning:</strong>
   * Serialized objects of this class will not be compatible with
   * future Swing releases. The current serialization support is
   * appropriate for short term storage or RMI between applications running
   * the same version of Swing.  As of 1.4, support for long term storage
   * of all JavaBeans&trade;
   * has been added to the <code>java.beans</code> package.
   * Please see {@link java.beans.XMLEncoder}.
   */
  public class AccessibleJTextComponent extends AccessibleJComponent
      implements AccessibleText, CaretListener, DocumentListener,
      AccessibleAction, AccessibleEditableText,
      AccessibleExtendedText {

    int caretPos;
    Point oldLocationOnScreen;

    /**
     * Constructs an AccessibleJTextComponent.  Adds a listener to track
     * caret change.
     */
    public AccessibleJTextComponent() {
      Document doc = JTextComponent.this.getDocument();
      if (doc != null) {
        doc.addDocumentListener(this);
      }
      JTextComponent.this.addCaretListener(this);
      caretPos = getCaretPosition();

      try {
        oldLocationOnScreen = getLocationOnScreen();
      } catch (IllegalComponentStateException iae) {
      }

      // Fire a ACCESSIBLE_VISIBLE_DATA_PROPERTY PropertyChangeEvent
      // when the text component moves (e.g., when scrolling).
      // Using an anonymous class since making AccessibleJTextComponent
      // implement ComponentListener would be an API change.
      JTextComponent.this.addComponentListener(new ComponentAdapter() {

        public void componentMoved(ComponentEvent e) {
          try {
            Point newLocationOnScreen = getLocationOnScreen();
            firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY,
                oldLocationOnScreen,
                newLocationOnScreen);

            oldLocationOnScreen = newLocationOnScreen;
          } catch (IllegalComponentStateException iae) {
          }
        }
      });
    }

    /**
     * Handles caret updates (fire appropriate property change event,
     * which are AccessibleContext.ACCESSIBLE_CARET_PROPERTY and
     * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY).
     * This keeps track of the dot position internally.  When the caret
     * moves, the internal position is updated after firing the event.
     *
     * @param e the CaretEvent
     */
    public void caretUpdate(CaretEvent e) {
      int dot = e.getDot();
      int mark = e.getMark();
      if (caretPos != dot) {
        // the caret moved
        firePropertyChange(ACCESSIBLE_CARET_PROPERTY,
            new Integer(caretPos), new Integer(dot));
        caretPos = dot;

        try {
          oldLocationOnScreen = getLocationOnScreen();
        } catch (IllegalComponentStateException iae) {
        }
      }
      if (mark != dot) {
        // there is a selection
        firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
            getSelectedText());
      }
    }

    // DocumentListener methods

    /**
     * Handles document insert (fire appropriate property change event
     * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
     * This tracks the changed offset via the event.
     *
     * @param e the DocumentEvent
     */
    public void insertUpdate(DocumentEvent e) {
      final Integer pos = new Integer(e.getOffset());
      if (SwingUtilities.isEventDispatchThread()) {
        firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
      } else {
        Runnable doFire = new Runnable() {
          public void run() {
            firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
                null, pos);
          }
        };
        SwingUtilities.invokeLater(doFire);
      }
    }

    /**
     * Handles document remove (fire appropriate property change event,
     * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
     * This tracks the changed offset via the event.
     *
     * @param e the DocumentEvent
     */
    public void removeUpdate(DocumentEvent e) {
      final Integer pos = new Integer(e.getOffset());
      if (SwingUtilities.isEventDispatchThread()) {
        firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
      } else {
        Runnable doFire = new Runnable() {
          public void run() {
            firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
                null, pos);
          }
        };
        SwingUtilities.invokeLater(doFire);
      }
    }

    /**
     * Handles document remove (fire appropriate property change event,
     * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
     * This tracks the changed offset via the event.
     *
     * @param e the DocumentEvent
     */
    public void changedUpdate(DocumentEvent e) {
      final Integer pos = new Integer(e.getOffset());
      if (SwingUtilities.isEventDispatchThread()) {
        firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
      } else {
        Runnable doFire = new Runnable() {
          public void run() {
            firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
                null, pos);
          }
        };
        SwingUtilities.invokeLater(doFire);
      }
    }

    /**
     * Gets the state set of the JTextComponent.
     * The AccessibleStateSet of an object is composed of a set of
     * unique AccessibleState's.  A change in the AccessibleStateSet
     * of an object will cause a PropertyChangeEvent to be fired
     * for the AccessibleContext.ACCESSIBLE_STATE_PROPERTY property.
     *
     * @return an instance of AccessibleStateSet containing the current state set of the object
     * @see AccessibleStateSet
     * @see AccessibleState
     * @see #addPropertyChangeListener
     */
    public AccessibleStateSet getAccessibleStateSet() {
      AccessibleStateSet states = super.getAccessibleStateSet();
      if (JTextComponent.this.isEditable()) {
        states.add(AccessibleState.EDITABLE);
      }
      return states;
    }


    /**
     * Gets the role of this object.
     *
     * @return an instance of AccessibleRole describing the role of the object (AccessibleRole.TEXT)
     * @see AccessibleRole
     */
    public AccessibleRole getAccessibleRole() {
      return AccessibleRole.TEXT;
    }

    /**
     * Get the AccessibleText associated with this object.  In the
     * implementation of the Java Accessibility API for this class,
     * return this object, which is responsible for implementing the
     * AccessibleText interface on behalf of itself.
     *
     * @return this object
     */
    public AccessibleText getAccessibleText() {
      return this;
    }

    // --- interface AccessibleText methods ------------------------

    /**
     * Many of these methods are just convenience methods; they
     * just call the equivalent on the parent
     */

    /**
     * Given a point in local coordinates, return the zero-based index
     * of the character under that Point.  If the point is invalid,
     * this method returns -1.
     *
     * @param p the Point in local coordinates
     * @return the zero-based index of the character under Point p.
     */
    public int getIndexAtPoint(Point p) {
      if (p == null) {
        return -1;
      }
      return JTextComponent.this.viewToModel(p);
    }

    /**
     * Gets the editor's drawing rectangle.  Stolen
     * from the unfortunately named
     * BasicTextUI.getVisibleEditorRect()
     *
     * @return the bounding box for the root view
     */
    Rectangle getRootEditorRect() {
      Rectangle alloc = JTextComponent.this.getBounds();
      if ((alloc.width > 0) && (alloc.height > 0)) {
        alloc.x = alloc.y = 0;
        Insets insets = JTextComponent.this.getInsets();
        alloc.x += insets.left;
        alloc.y += insets.top;
        alloc.width -= insets.left + insets.right;
        alloc.height -= insets.top + insets.bottom;
        return alloc;
      }
      return null;
    }

    /**
     * Determines the bounding box of the character at the given
     * index into the string.  The bounds are returned in local
     * coordinates.  If the index is invalid a null rectangle
     * is returned.
     *
     * The screen coordinates returned are "unscrolled coordinates"
     * if the JTextComponent is contained in a JScrollPane in which
     * case the resulting rectangle should be composed with the parent
     * coordinates.  A good algorithm to use is:
     * <pre>
     * Accessible a:
     * AccessibleText at = a.getAccessibleText();
     * AccessibleComponent ac = a.getAccessibleComponent();
     * Rectangle r = at.getCharacterBounds();
     * Point p = ac.getLocation();
     * r.x += p.x;
     * r.y += p.y;
     * </pre>
     *
     * Note: the JTextComponent must have a valid size (e.g. have
     * been added to a parent container whose ancestor container
     * is a valid top-level window) for this method to be able
     * to return a meaningful (non-null) value.
     *
     * @param i the index into the String &ge; 0
     * @return the screen coordinates of the character's bounding box
     */
    public Rectangle getCharacterBounds(int i) {
      if (i < 0 || i > model.getLength() - 1) {
        return null;
      }
      TextUI ui = getUI();
      if (ui == null) {
        return null;
      }
      Rectangle rect = null;
      Rectangle alloc = getRootEditorRect();
      if (alloc == null) {
        return null;
      }
      if (model instanceof AbstractDocument) {
        ((AbstractDocument) model).readLock();
      }
      try {
        View rootView = ui.getRootView(JTextComponent.this);
        if (rootView != null) {
          rootView.setSize(alloc.width, alloc.height);

          Shape bounds = rootView.modelToView(i,
              Position.Bias.Forward, i + 1,
              Position.Bias.Backward, alloc);

          rect = (bounds instanceof Rectangle) ?
              (Rectangle) bounds : bounds.getBounds();

        }
      } catch (BadLocationException e) {
      } finally {
        if (model instanceof AbstractDocument) {
          ((AbstractDocument) model).readUnlock();
        }
      }
      return rect;
    }

    /**
     * Returns the number of characters (valid indices)
     *
     * @return the number of characters &ge; 0
     */
    public int getCharCount() {
      return model.getLength();
    }

    /**
     * Returns the zero-based offset of the caret.
     *
     * Note: The character to the right of the caret will have the
     * same index value as the offset (the caret is between
     * two characters).
     *
     * @return the zero-based offset of the caret.
     */
    public int getCaretPosition() {
      return JTextComponent.this.getCaretPosition();
    }

    /**
     * Returns the AttributeSet for a given character (at a given index).
     *
     * @param i the zero-based index into the text
     * @return the AttributeSet of the character
     */
    public AttributeSet getCharacterAttribute(int i) {
      Element e = null;
      if (model instanceof AbstractDocument) {
        ((AbstractDocument) model).readLock();
      }
      try {
        for (e = model.getDefaultRootElement(); !e.isLeaf(); ) {
          int index = e.getElementIndex(i);
          e = e.getElement(index);
        }
      } finally {
        if (model instanceof AbstractDocument) {
          ((AbstractDocument) model).readUnlock();
        }
      }
      return e.getAttributes();
    }


    /**
     * Returns the start offset within the selected text.
     * If there is no selection, but there is
     * a caret, the start and end offsets will be the same.
     * Return 0 if the text is empty, or the caret position
     * if no selection.
     *
     * @return the index into the text of the start of the selection &ge; 0
     */
    public int getSelectionStart() {
      return JTextComponent.this.getSelectionStart();
    }

    /**
     * Returns the end offset within the selected text.
     * If there is no selection, but there is
     * a caret, the start and end offsets will be the same.
     * Return 0 if the text is empty, or the caret position
     * if no selection.
     *
     * @return the index into the text of the end of the selection &ge; 0
     */
    public int getSelectionEnd() {
      return JTextComponent.this.getSelectionEnd();
    }

    /**
     * Returns the portion of the text that is selected.
     *
     * @return the text, null if no selection
     */
    public String getSelectedText() {
      return JTextComponent.this.getSelectedText();
    }

    /**
     * IndexedSegment extends Segment adding the offset into the
     * the model the <code>Segment</code> was asked for.
     */
    private class IndexedSegment extends Segment {

      /**
       * Offset into the model that the position represents.
       */
      public int modelOffset;
    }

    // TIGER - 4170173

    /**
     * Returns the String at a given index. Whitespace
     * between words is treated as a word.
     *
     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
     * @param index an index within the text
     * @return the letter, word, or sentence.
     */
    public String getAtIndex(int part, int index) {
      return getAtIndex(part, index, 0);
    }


    /**
     * Returns the String after a given index. Whitespace
     * between words is treated as a word.
     *
     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
     * @param index an index within the text
     * @return the letter, word, or sentence.
     */
    public String getAfterIndex(int part, int index) {
      return getAtIndex(part, index, 1);
    }


    /**
     * Returns the String before a given index. Whitespace
     * between words is treated a word.
     *
     * @param part the CHARACTER, WORD, or SENTENCE to retrieve
     * @param index an index within the text
     * @return the letter, word, or sentence.
     */
    public String getBeforeIndex(int part, int index) {
      return getAtIndex(part, index, -1);
    }


    /**
     * Gets the word, sentence, or character at <code>index</code>.
     * If <code>direction</code> is non-null this will find the
     * next/previous word/sentence/character.
     */
    private String getAtIndex(int part, int index, int direction) {
      if (model instanceof AbstractDocument) {
        ((AbstractDocument) model).readLock();
      }
      try {
        if (index < 0 || index >= model.getLength()) {
          return null;
        }
        switch (part) {
          case AccessibleText.CHARACTER:
            if (index + direction < model.getLength() &&
                index + direction >= 0) {
              return model.getText(index + direction, 1);
            }
            break;

          case AccessibleText.WORD:
          case AccessibleText.SENTENCE:
            IndexedSegment seg = getSegmentAt(part, index);
            if (seg != null) {
              if (direction != 0) {
                int next;

                if (direction < 0) {
                  next = seg.modelOffset - 1;
                } else {
                  next = seg.modelOffset + direction * seg.count;
                }
                if (next >= 0 && next <= model.getLength()) {
                  seg = getSegmentAt(part, next);
                } else {
                  seg = null;
                }
              }
              if (seg != null) {
                return new String(seg.array, seg.offset,
                    seg.count);
              }
            }
            break;

          default:
            break;
        }
      } catch (BadLocationException e) {
      } finally {
        if (model instanceof AbstractDocument) {
          ((AbstractDocument) model).readUnlock();
        }
      }
      return null;
    }


    /*
         * Returns the paragraph element for the specified index.
         */
    private Element getParagraphElement(int index) {
      if (model instanceof PlainDocument) {
        PlainDocument sdoc = (PlainDocument) model;
        return sdoc.getParagraphElement(index);
      } else if (model instanceof StyledDocument) {
        StyledDocument sdoc = (StyledDocument) model;
        return sdoc.getParagraphElement(index);
      } else {
        Element para;
        for (para = model.getDefaultRootElement(); !para.isLeaf(); ) {
          int pos = para.getElementIndex(index);
          para = para.getElement(pos);
        }
        if (para == null) {
          return null;
        }
        return para.getParentElement();
      }
    }

    /*
         * Returns a <code>Segment</code> containing the paragraph text
         * at <code>index</code>, or null if <code>index</code> isn't
         * valid.
         */
    private IndexedSegment getParagraphElementText(int index)
        throws BadLocationException {
      Element para = getParagraphElement(index);

      if (para != null) {
        IndexedSegment segment = new IndexedSegment();
        try {
          int length = para.getEndOffset() - para.getStartOffset();
          model.getText(para.getStartOffset(), length, segment);
        } catch (BadLocationException e) {
          return null;
        }
        segment.modelOffset = para.getStartOffset();
        return segment;
      }
      return null;
    }


    /**
     * Returns the Segment at <code>index</code> representing either
     * the paragraph or sentence as identified by <code>part</code>, or
     * null if a valid paragraph/sentence can't be found. The offset
     * will point to the start of the word/sentence in the array, and
     * the modelOffset will point to the location of the word/sentence
     * in the model.
     */
    private IndexedSegment getSegmentAt(int part, int index) throws
        BadLocationException {
      IndexedSegment seg = getParagraphElementText(index);
      if (seg == null) {
        return null;
      }
      BreakIterator iterator;
      switch (part) {
        case AccessibleText.WORD:
          iterator = BreakIterator.getWordInstance(getLocale());
          break;
        case AccessibleText.SENTENCE:
          iterator = BreakIterator.getSentenceInstance(getLocale());
          break;
        default:
          return null;
      }
      seg.first();
      iterator.setText(seg);
      int end = iterator.following(index - seg.modelOffset + seg.offset);
      if (end == BreakIterator.DONE) {
        return null;
      }
      if (end > seg.offset + seg.count) {
        return null;
      }
      int begin = iterator.previous();
      if (begin == BreakIterator.DONE ||
          begin >= seg.offset + seg.count) {
        return null;
      }
      seg.modelOffset = seg.modelOffset + begin - seg.offset;
      seg.offset = begin;
      seg.count = end - begin;
      return seg;
    }

    // begin AccessibleEditableText methods -----

    /**
     * Returns the AccessibleEditableText interface for
     * this text component.
     *
     * @return the AccessibleEditableText interface
     * @since 1.4
     */
    public AccessibleEditableText getAccessibleEditableText() {
      return this;
    }

    /**
     * Sets the text contents to the specified string.
     *
     * @param s the string to set the text contents
     * @since 1.4
     */
    public void setTextContents(String s) {
      JTextComponent.this.setText(s);
    }

    /**
     * Inserts the specified string at the given index
     *
     * @param index the index in the text where the string will be inserted
     * @param s the string to insert in the text
     * @since 1.4
     */
    public void insertTextAtIndex(int index, String s) {
      Document doc = JTextComponent.this.getDocument();
      if (doc != null) {
        try {
          if (s != null && s.length() > 0) {
            boolean composedTextSaved = saveComposedText(index);
            doc.insertString(index, s, null);
            if (composedTextSaved) {
              restoreComposedText();
            }
          }
        } catch (BadLocationException e) {
          UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
        }
      }
    }

    /**
     * Returns the text string between two indices.
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @return the text string between the indices
     * @since 1.4
     */
    public String getTextRange(int startIndex, int endIndex) {
      String txt = null;
      int p0 = Math.min(startIndex, endIndex);
      int p1 = Math.max(startIndex, endIndex);
      if (p0 != p1) {
        try {
          Document doc = JTextComponent.this.getDocument();
          txt = doc.getText(p0, p1 - p0);
        } catch (BadLocationException e) {
          throw new IllegalArgumentException(e.getMessage());
        }
      }
      return txt;
    }

    /**
     * Deletes the text between two indices
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @since 1.4
     */
    public void delete(int startIndex, int endIndex) {
      if (isEditable() && isEnabled()) {
        try {
          int p0 = Math.min(startIndex, endIndex);
          int p1 = Math.max(startIndex, endIndex);
          if (p0 != p1) {
            Document doc = getDocument();
            doc.remove(p0, p1 - p0);
          }
        } catch (BadLocationException e) {
        }
      } else {
        UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
      }
    }

    /**
     * Cuts the text between two indices into the system clipboard.
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @since 1.4
     */
    public void cut(int startIndex, int endIndex) {
      selectText(startIndex, endIndex);
      JTextComponent.this.cut();
    }

    /**
     * Pastes the text from the system clipboard into the text
     * starting at the specified index.
     *
     * @param startIndex the starting index in the text
     * @since 1.4
     */
    public void paste(int startIndex) {
      setCaretPosition(startIndex);
      JTextComponent.this.paste();
    }

    /**
     * Replaces the text between two indices with the specified
     * string.
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @param s the string to replace the text between two indices
     * @since 1.4
     */
    public void replaceText(int startIndex, int endIndex, String s) {
      selectText(startIndex, endIndex);
      JTextComponent.this.replaceSelection(s);
    }

    /**
     * Selects the text between two indices.
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @since 1.4
     */
    public void selectText(int startIndex, int endIndex) {
      JTextComponent.this.select(startIndex, endIndex);
    }

    /**
     * Sets attributes for the text between two indices.
     *
     * @param startIndex the starting index in the text
     * @param endIndex the ending index in the text
     * @param as the attribute set
     * @see AttributeSet
     * @since 1.4
     */
    public void setAttributes(int startIndex, int endIndex,
        AttributeSet as) {

      // Fixes bug 4487492
      Document doc = JTextComponent.this.getDocument();
      if (doc != null && doc instanceof StyledDocument) {
        StyledDocument sDoc = (StyledDocument) doc;
        int offset = startIndex;
        int length = endIndex - startIndex;
        sDoc.setCharacterAttributes(offset, length, as, true);
      }
    }

    // ----- end AccessibleEditableText methods

    // ----- begin AccessibleExtendedText methods

// Probably should replace the helper method getAtIndex() to return
// instead an AccessibleTextSequence also for LINE & ATTRIBUTE_RUN
// and then make the AccessibleText methods get[At|After|Before]Point
// call this new method instead and return only the string portion

    /**
     * Returns the AccessibleTextSequence at a given <code>index</code>.
     * If <code>direction</code> is non-null this will find the
     * next/previous word/sentence/character.
     *
     * @param part the <code>CHARACTER</code>, <code>WORD</code>, <code>SENTENCE</code>,
     * <code>LINE</code> or <code>ATTRIBUTE_RUN</code> to retrieve
     * @param index an index within the text
     * @param direction is either -1, 0, or 1
     * @return an <code>AccessibleTextSequence</code> specifying the text if <code>part</code> and
     * <code>index</code> are valid.  Otherwise, <code>null</code> is returned.
     * @see javax.accessibility.AccessibleText#CHARACTER
     * @see javax.accessibility.AccessibleText#WORD
     * @see javax.accessibility.AccessibleText#SENTENCE
     * @see javax.accessibility.AccessibleExtendedText#LINE
     * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
     * @since 1.6
     */
    private AccessibleTextSequence getSequenceAtIndex(int part,
        int index, int direction) {
      if (index < 0 || index >= model.getLength()) {
        return null;
      }
      if (direction < -1 || direction > 1) {
        return null;    // direction must be 1, 0, or -1
      }

      switch (part) {
        case AccessibleText.CHARACTER:
          if (model instanceof AbstractDocument) {
            ((AbstractDocument) model).readLock();
          }
          AccessibleTextSequence charSequence = null;
          try {
            if (index + direction < model.getLength() &&
                index + direction >= 0) {
              charSequence =
                  new AccessibleTextSequence(index + direction,
                      index + direction + 1,
                      model.getText(index + direction, 1));
            }

          } catch (BadLocationException e) {
            // we are intentionally silent; our contract says we return
            // null if there is any failure in this method
          } finally {
            if (model instanceof AbstractDocument) {
              ((AbstractDocument) model).readUnlock();
            }
          }
          return charSequence;

        case AccessibleText.WORD:
        case AccessibleText.SENTENCE:
          if (model instanceof AbstractDocument) {
            ((AbstractDocument) model).readLock();
          }
          AccessibleTextSequence rangeSequence = null;
          try {
            IndexedSegment seg = getSegmentAt(part, index);
            if (seg != null) {
              if (direction != 0) {
                int next;

                if (direction < 0) {
                  next = seg.modelOffset - 1;
                } else {
                  next = seg.modelOffset + seg.count;
                }
                if (next >= 0 && next <= model.getLength()) {
                  seg = getSegmentAt(part, next);
                } else {
                  seg = null;
                }
              }
              if (seg != null &&
                  (seg.offset + seg.count) <= model.getLength()) {
                rangeSequence =
                    new AccessibleTextSequence(seg.offset,
                        seg.offset + seg.count,
                        new String(seg.array, seg.offset, seg.count));
              } // else we leave rangeSequence set to null
            }
          } catch (BadLocationException e) {
            // we are intentionally silent; our contract says we return
            // null if there is any failure in this method
          } finally {
            if (model instanceof AbstractDocument) {
              ((AbstractDocument) model).readUnlock();
            }
          }
          return rangeSequence;

        case AccessibleExtendedText.LINE:
          AccessibleTextSequence lineSequence = null;
          if (model instanceof AbstractDocument) {
            ((AbstractDocument) model).readLock();
          }
          try {
            int startIndex =
                Utilities.getRowStart(JTextComponent.this, index);
            int endIndex =
                Utilities.getRowEnd(JTextComponent.this, index);
            if (startIndex >= 0 && endIndex >= startIndex) {
              if (direction == 0) {
                lineSequence =
                    new AccessibleTextSequence(startIndex, endIndex,
                        model.getText(startIndex,
                            endIndex - startIndex + 1));
              } else if (direction == -1 && startIndex > 0) {
                endIndex =
                    Utilities.getRowEnd(JTextComponent.this,
                        startIndex - 1);
                startIndex =
                    Utilities.getRowStart(JTextComponent.this,
                        startIndex - 1);
                if (startIndex >= 0 && endIndex >= startIndex) {
                  lineSequence =
                      new AccessibleTextSequence(startIndex,
                          endIndex,
                          model.getText(startIndex,
                              endIndex - startIndex + 1));
                }
              } else if (direction == 1 &&
                  endIndex < model.getLength()) {
                startIndex =
                    Utilities.getRowStart(JTextComponent.this,
                        endIndex + 1);
                endIndex =
                    Utilities.getRowEnd(JTextComponent.this,
                        endIndex + 1);
                if (startIndex >= 0 && endIndex >= startIndex) {
                  lineSequence =
                      new AccessibleTextSequence(startIndex,
                          endIndex, model.getText(startIndex,
                          endIndex - startIndex + 1));
                }
              }
              // already validated 'direction' above...
            }
          } catch (BadLocationException e) {
            // we are intentionally silent; our contract says we return
            // null if there is any failure in this method
          } finally {
            if (model instanceof AbstractDocument) {
              ((AbstractDocument) model).readUnlock();
            }
          }
          return lineSequence;

        case AccessibleExtendedText.ATTRIBUTE_RUN:
          // assumptions: (1) that all characters in a single element
          // share the same attribute set; (2) that adjacent elements
          // *may* share the same attribute set

          int attributeRunStartIndex, attributeRunEndIndex;
          String runText = null;
          if (model instanceof AbstractDocument) {
            ((AbstractDocument) model).readLock();
          }

          try {
            attributeRunStartIndex = attributeRunEndIndex =
                Integer.MIN_VALUE;
            int tempIndex = index;
            switch (direction) {
              case -1:
                // going backwards, so find left edge of this run -
                // that'll be the end of the previous run
                // (off-by-one counting)
                attributeRunEndIndex = getRunEdge(index, direction);
                // now set ourselves up to find the left edge of the
                // prev. run
                tempIndex = attributeRunEndIndex - 1;
                break;
              case 1:
                // going forward, so find right edge of this run -
                // that'll be the start of the next run
                // (off-by-one counting)
                attributeRunStartIndex = getRunEdge(index, direction);
                // now set ourselves up to find the right edge of the
                // next run
                tempIndex = attributeRunStartIndex;
                break;
              case 0:
                // interested in the current run, so nothing special to
                // set up in advance...
                break;
              default:
                // only those three values of direction allowed...
                throw new AssertionError(direction);
            }

            // set the unset edge; if neither set then we're getting
            // both edges of the current run around our 'index'
            attributeRunStartIndex =
                (attributeRunStartIndex != Integer.MIN_VALUE) ?
                    attributeRunStartIndex : getRunEdge(tempIndex, -1);
            attributeRunEndIndex =
                (attributeRunEndIndex != Integer.MIN_VALUE) ?
                    attributeRunEndIndex : getRunEdge(tempIndex, 1);

            runText = model.getText(attributeRunStartIndex,
                attributeRunEndIndex -
                    attributeRunStartIndex);
          } catch (BadLocationException e) {
            // we are intentionally silent; our contract says we return
            // null if there is any failure in this method
            return null;
          } finally {
            if (model instanceof AbstractDocument) {
              ((AbstractDocument) model).readUnlock();
            }
          }
          return new AccessibleTextSequence(attributeRunStartIndex,
              attributeRunEndIndex,
              runText);

        default:
          break;
      }
      return null;
    }


    /**
     * Starting at text position <code>index</code>, and going in
     * <code>direction</code>, return the edge of run that shares the
     * same <code>AttributeSet</code> and parent element as those at
     * <code>index</code>.
     *
     * Note: we assume the document is already locked...
     */
    private int getRunEdge(int index, int direction) throws
        BadLocationException {
      if (index < 0 || index >= model.getLength()) {
        throw new BadLocationException("Location out of bounds", index);
      }
      // locate the Element at index
      Element indexElement;
      // locate the Element at our index/offset
      int elementIndex = -1;        // test for initialization
      for (indexElement = model.getDefaultRootElement();
          !indexElement.isLeaf(); ) {
        elementIndex = indexElement.getElementIndex(index);
        indexElement = indexElement.getElement(elementIndex);
      }
      if (elementIndex == -1) {
        throw new AssertionError(index);
      }
      // cache the AttributeSet and parentElement atindex
      AttributeSet indexAS = indexElement.getAttributes();
      Element parent = indexElement.getParentElement();

      // find the first Element before/after ours w/the same AttributeSet
      // if we are already at edge of the first element in our parent
      // then return that edge
      Element edgeElement;
      switch (direction) {
        case -1:
        case 1:
          int edgeElementIndex = elementIndex;
          int elementCount = parent.getElementCount();
          while ((edgeElementIndex + direction) > 0 &&
              ((edgeElementIndex + direction) < elementCount) &&
              parent.getElement(edgeElementIndex
                  + direction).getAttributes().isEqual(indexAS)) {
            edgeElementIndex += direction;
          }
          edgeElement = parent.getElement(edgeElementIndex);
          break;
        default:
          throw new AssertionError(direction);
      }
      switch (direction) {
        case -1:
          return edgeElement.getStartOffset();
        case 1:
          return edgeElement.getEndOffset();
        default:
          // we already caught this case earlier; this is to satisfy
          // the compiler...
          return Integer.MIN_VALUE;
      }
    }

    // getTextRange() not needed; defined in AccessibleEditableText

    /**
     * Returns the <code>AccessibleTextSequence</code> at a given
     * <code>index</code>.
     *
     * @param part the <code>CHARACTER</code>, <code>WORD</code>, <code>SENTENCE</code>,
     * <code>LINE</code> or <code>ATTRIBUTE_RUN</code> to retrieve
     * @param index an index within the text
     * @return an <code>AccessibleTextSequence</code> specifying the text if <code>part</code> and
     * <code>index</code> are valid.  Otherwise, <code>null</code> is returned
     * @see javax.accessibility.AccessibleText#CHARACTER
     * @see javax.accessibility.AccessibleText#WORD
     * @see javax.accessibility.AccessibleText#SENTENCE
     * @see javax.accessibility.AccessibleExtendedText#LINE
     * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
     * @since 1.6
     */
    public AccessibleTextSequence getTextSequenceAt(int part, int index) {
      return getSequenceAtIndex(part, index, 0);
    }

    /**
     * Returns the <code>AccessibleTextSequence</code> after a given
     * <code>index</code>.
     *
     * @param part the <code>CHARACTER</code>, <code>WORD</code>, <code>SENTENCE</code>,
     * <code>LINE</code> or <code>ATTRIBUTE_RUN</code> to retrieve
     * @param index an index within the text
     * @return an <code>AccessibleTextSequence</code> specifying the text if <code>part</code> and
     * <code>index</code> are valid.  Otherwise, <code>null</code> is returned
     * @see javax.accessibility.AccessibleText#CHARACTER
     * @see javax.accessibility.AccessibleText#WORD
     * @see javax.accessibility.AccessibleText#SENTENCE
     * @see javax.accessibility.AccessibleExtendedText#LINE
     * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
     * @since 1.6
     */
    public AccessibleTextSequence getTextSequenceAfter(int part, int index) {
      return getSequenceAtIndex(part, index, 1);
    }

    /**
     * Returns the <code>AccessibleTextSequence</code> before a given
     * <code>index</code>.
     *
     * @param part the <code>CHARACTER</code>, <code>WORD</code>, <code>SENTENCE</code>,
     * <code>LINE</code> or <code>ATTRIBUTE_RUN</code> to retrieve
     * @param index an index within the text
     * @return an <code>AccessibleTextSequence</code> specifying the text if <code>part</code> and
     * <code>index</code> are valid.  Otherwise, <code>null</code> is returned
     * @see javax.accessibility.AccessibleText#CHARACTER
     * @see javax.accessibility.AccessibleText#WORD
     * @see javax.accessibility.AccessibleText#SENTENCE
     * @see javax.accessibility.AccessibleExtendedText#LINE
     * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
     * @since 1.6
     */
    public AccessibleTextSequence getTextSequenceBefore(int part, int index) {
      return getSequenceAtIndex(part, index, -1);
    }

    /**
     * Returns the <code>Rectangle</code> enclosing the text between
     * two indicies.
     *
     * @param startIndex the start index in the text
     * @param endIndex the end index in the text
     * @return the bounding rectangle of the text if the indices are valid. Otherwise,
     * <code>null</code> is returned
     * @since 1.6
     */
    public Rectangle getTextBounds(int startIndex, int endIndex) {
      if (startIndex < 0 || startIndex > model.getLength() - 1 ||
          endIndex < 0 || endIndex > model.getLength() - 1 ||
          startIndex > endIndex) {
        return null;
      }
      TextUI ui = getUI();
      if (ui == null) {
        return null;
      }
      Rectangle rect = null;
      Rectangle alloc = getRootEditorRect();
      if (alloc == null) {
        return null;
      }
      if (model instanceof AbstractDocument) {
        ((AbstractDocument) model).readLock();
      }
      try {
        View rootView = ui.getRootView(JTextComponent.this);
        if (rootView != null) {
          Shape bounds = rootView.modelToView(startIndex,
              Position.Bias.Forward, endIndex,
              Position.Bias.Backward, alloc);

          rect = (bounds instanceof Rectangle) ?
              (Rectangle) bounds : bounds.getBounds();

        }
      } catch (BadLocationException e) {
      } finally {
        if (model instanceof AbstractDocument) {
          ((AbstractDocument) model).readUnlock();
        }
      }
      return rect;
    }

    // ----- end AccessibleExtendedText methods

    // --- interface AccessibleAction methods ------------------------

    public AccessibleAction getAccessibleAction() {
      return this;
    }

    /**
     * Returns the number of accessible actions available in this object
     * If there are more than one, the first one is considered the
     * "default" action of the object.
     *
     * @return the zero-based number of Actions in this object
     * @since 1.4
     */
    public int getAccessibleActionCount() {
      Action[] actions = JTextComponent.this.getActions();
      return actions.length;
    }

    /**
     * Returns a description of the specified action of the object.
     *
     * @param i zero-based index of the actions
     * @return a String description of the action
     * @see #getAccessibleActionCount
     * @since 1.4
     */
    public String getAccessibleActionDescription(int i) {
      Action[] actions = JTextComponent.this.getActions();
      if (i < 0 || i >= actions.length) {
        return null;
      }
      return (String) actions[i].getValue(Action.NAME);
    }

    /**
     * Performs the specified Action on the object
     *
     * @param i zero-based index of actions
     * @return true if the action was performed; otherwise false.
     * @see #getAccessibleActionCount
     * @since 1.4
     */
    public boolean doAccessibleAction(int i) {
      Action[] actions = JTextComponent.this.getActions();
      if (i < 0 || i >= actions.length) {
        return false;
      }
      ActionEvent ae =
          new ActionEvent(JTextComponent.this,
              ActionEvent.ACTION_PERFORMED, null,
              EventQueue.getMostRecentEventTime(),
              getCurrentEventModifiers());
      actions[i].actionPerformed(ae);
      return true;
    }

    // ----- end AccessibleAction methods


  }

  // --- serialization ---------------------------------------------

  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    caretEvent = new MutableCaretEvent(this);
    addMouseListener(caretEvent);
    addFocusListener(caretEvent);
  }

  // --- member variables ----------------------------------

  /**
   * The document model.
   */
  private Document model;

  /**
   * The caret used to display the insert position
   * and navigate throughout the document.
   *
   * PENDING(prinz)
   * This should be serializable, default installed
   * by UI.
   */
  private transient Caret caret;

  /**
   * Object responsible for restricting the cursor navigation.
   */
  private NavigationFilter navigationFilter;

  /**
   * The object responsible for managing highlights.
   *
   * PENDING(prinz)
   * This should be serializable, default installed
   * by UI.
   */
  private transient Highlighter highlighter;

  /**
   * The current key bindings in effect.
   *
   * PENDING(prinz)
   * This should be serializable, default installed
   * by UI.
   */
  private transient Keymap keymap;

  private transient MutableCaretEvent caretEvent;
  private Color caretColor;
  private Color selectionColor;
  private Color selectedTextColor;
  private Color disabledTextColor;
  private boolean editable;
  private Insets margin;
  private char focusAccelerator;
  private boolean dragEnabled;

  /**
   * The drop mode for this component.
   */
  private DropMode dropMode = DropMode.USE_SELECTION;

  /**
   * The drop location.
   */
  private transient DropLocation dropLocation;

  /**
   * Represents a drop location for <code>JTextComponent</code>s.
   *
   * @see #getDropLocation
   * @since 1.6
   */
  public static final class DropLocation extends TransferHandler.DropLocation {

    private final int index;
    private final Position.Bias bias;

    private DropLocation(Point p, int index, Position.Bias bias) {
      super(p);
      this.index = index;
      this.bias = bias;
    }

    /**
     * Returns the index where dropped data should be inserted into the
     * associated component. This index represents a position between
     * characters, as would be interpreted by a caret.
     *
     * @return the drop index
     */
    public int getIndex() {
      return index;
    }

    /**
     * Returns the bias for the drop index.
     *
     * @return the drop bias
     */
    public Position.Bias getBias() {
      return bias;
    }

    /**
     * Returns a string representation of this drop location.
     * This method is intended to be used for debugging purposes,
     * and the content and format of the returned string may vary
     * between implementations.
     *
     * @return a string representation of this drop location
     */
    public String toString() {
      return getClass().getName()
          + "[dropPoint=" + getDropPoint() + ","
          + "index=" + index + ","
          + "bias=" + bias + "]";
    }
  }

  /**
   * TransferHandler used if one hasn't been supplied by the UI.
   */
  private static DefaultTransferHandler defaultTransferHandler;

  /**
   * Maps from class name to Boolean indicating if
   * <code>processInputMethodEvent</code> has been overriden.
   */
  private static Cache<Class<?>, Boolean> METHOD_OVERRIDDEN
      = new Cache<Class<?>, Boolean>(Cache.Kind.WEAK, Cache.Kind.STRONG) {
    /**
     * Returns {@code true} if the specified {@code type} extends {@link JTextComponent}
     * and the {@link JTextComponent#processInputMethodEvent} method is overridden.
     */
    @Override
    public Boolean create(final Class<?> type) {
      if (JTextComponent.class == type) {
        return Boolean.FALSE;
      }
      if (get(type.getSuperclass())) {
        return Boolean.TRUE;
      }
      return AccessController.doPrivileged(
          new PrivilegedAction<Boolean>() {
            public Boolean run() {
              try {
                type.getDeclaredMethod("processInputMethodEvent", InputMethodEvent.class);
                return Boolean.TRUE;
              } catch (NoSuchMethodException exception) {
                return Boolean.FALSE;
              }
            }
          });
    }
  };

  /**
   * Returns a string representation of this <code>JTextComponent</code>.
   * This method is intended to be used only for debugging purposes, and the
   * content and format of the returned string may vary between
   * implementations. The returned string may be empty but may not
   * be <code>null</code>.
   * <P>
   * Overriding <code>paramString</code> to provide information about the
   * specific new aspects of the JFC components.
   *
   * @return a string representation of this <code>JTextComponent</code>
   */
  protected String paramString() {
    String editableString = (editable ?
        "true" : "false");
    String caretColorString = (caretColor != null ?
        caretColor.toString() : "");
    String selectionColorString = (selectionColor != null ?
        selectionColor.toString() : "");
    String selectedTextColorString = (selectedTextColor != null ?
        selectedTextColor.toString() : "");
    String disabledTextColorString = (disabledTextColor != null ?
        disabledTextColor.toString() : "");
    String marginString = (margin != null ?
        margin.toString() : "");

    return super.paramString() +
        ",caretColor=" + caretColorString +
        ",disabledTextColor=" + disabledTextColorString +
        ",editable=" + editableString +
        ",margin=" + marginString +
        ",selectedTextColor=" + selectedTextColorString +
        ",selectionColor=" + selectionColorString;
  }


  /**
   * A Simple TransferHandler that exports the data as a String, and
   * imports the data from the String clipboard.  This is only used
   * if the UI hasn't supplied one, which would only happen if someone
   * hasn't subclassed Basic.
   */
  static class DefaultTransferHandler extends TransferHandler implements
      UIResource {

    public void exportToClipboard(JComponent comp, Clipboard clipboard,
        int action) throws IllegalStateException {
      if (comp instanceof JTextComponent) {
        JTextComponent text = (JTextComponent) comp;
        int p0 = text.getSelectionStart();
        int p1 = text.getSelectionEnd();
        if (p0 != p1) {
          try {
            Document doc = text.getDocument();
            String srcData = doc.getText(p0, p1 - p0);
            StringSelection contents = new StringSelection(srcData);

            // this may throw an IllegalStateException,
            // but it will be caught and handled in the
            // action that invoked this method
            clipboard.setContents(contents, null);

            if (action == TransferHandler.MOVE) {
              doc.remove(p0, p1 - p0);
            }
          } catch (BadLocationException ble) {
          }
        }
      }
    }

    public boolean importData(JComponent comp, Transferable t) {
      if (comp instanceof JTextComponent) {
        DataFlavor flavor = getFlavor(t.getTransferDataFlavors());

        if (flavor != null) {
          InputContext ic = comp.getInputContext();
          if (ic != null) {
            ic.endComposition();
          }
          try {
            String data = (String) t.getTransferData(flavor);

            ((JTextComponent) comp).replaceSelection(data);
            return true;
          } catch (UnsupportedFlavorException ufe) {
          } catch (IOException ioe) {
          }
        }
      }
      return false;
    }

    public boolean canImport(JComponent comp,
        DataFlavor[] transferFlavors) {
      JTextComponent c = (JTextComponent) comp;
      if (!(c.isEditable() && c.isEnabled())) {
        return false;
      }
      return (getFlavor(transferFlavors) != null);
    }

    public int getSourceActions(JComponent c) {
      return NONE;
    }

    private DataFlavor getFlavor(DataFlavor[] flavors) {
      if (flavors != null) {
        for (DataFlavor flavor : flavors) {
          if (flavor.equals(DataFlavor.stringFlavor)) {
            return flavor;
          }
        }
      }
      return null;
    }
  }

  /**
   * Returns the JTextComponent that most recently had focus. The returned
   * value may currently have focus.
   */
  static final JTextComponent getFocusedComponent() {
    return (JTextComponent) AppContext.getAppContext().
        get(FOCUSED_COMPONENT);
  }

  private int getCurrentEventModifiers() {
    int modifiers = 0;
    AWTEvent currentEvent = EventQueue.getCurrentEvent();
    if (currentEvent instanceof InputEvent) {
      modifiers = ((InputEvent) currentEvent).getModifiers();
    } else if (currentEvent instanceof ActionEvent) {
      modifiers = ((ActionEvent) currentEvent).getModifiers();
    }
    return modifiers;
  }

  private static final Object KEYMAP_TABLE =
      new StringBuilder("JTextComponent_KeymapTable");

  //
  // member variables used for on-the-spot input method
  // editing style support
  //
  private transient InputMethodRequests inputMethodRequestsHandler;
  private SimpleAttributeSet composedTextAttribute;
  private String composedTextContent;
  private Position composedTextStart;
  private Position composedTextEnd;
  private Position latestCommittedTextStart;
  private Position latestCommittedTextEnd;
  private ComposedTextCaret composedTextCaret;
  private transient Caret originalCaret;
  /**
   * Set to true after the check for the override of processInputMethodEvent
   * has been checked.
   */
  private boolean checkedInputOverride;
  private boolean needToSendKeyTypedEvent;

  static class DefaultKeymap implements Keymap {

    DefaultKeymap(String nm, Keymap parent) {
      this.nm = nm;
      this.parent = parent;
      bindings = new Hashtable<KeyStroke, Action>();
    }

    /**
     * Fetch the default action to fire if a
     * key is typed (ie a KEY_TYPED KeyEvent is received)
     * and there is no binding for it.  Typically this
     * would be some action that inserts text so that
     * the keymap doesn't require an action for each
     * possible key.
     */
    public Action getDefaultAction() {
      if (defaultAction != null) {
        return defaultAction;
      }
      return (parent != null) ? parent.getDefaultAction() : null;
    }

    /**
     * Set the default action to fire if a key is typed.
     */
    public void setDefaultAction(Action a) {
      defaultAction = a;
    }

    public String getName() {
      return nm;
    }

    public Action getAction(KeyStroke key) {
      Action a = bindings.get(key);
      if ((a == null) && (parent != null)) {
        a = parent.getAction(key);
      }
      return a;
    }

    public KeyStroke[] getBoundKeyStrokes() {
      KeyStroke[] keys = new KeyStroke[bindings.size()];
      int i = 0;
      for (Enumeration<KeyStroke> e = bindings.keys(); e.hasMoreElements(); ) {
        keys[i++] = e.nextElement();
      }
      return keys;
    }

    public Action[] getBoundActions() {
      Action[] actions = new Action[bindings.size()];
      int i = 0;
      for (Enumeration<Action> e = bindings.elements(); e.hasMoreElements(); ) {
        actions[i++] = e.nextElement();
      }
      return actions;
    }

    public KeyStroke[] getKeyStrokesForAction(Action a) {
      if (a == null) {
        return null;
      }
      KeyStroke[] retValue = null;
      // Determine local bindings first.
      Vector<KeyStroke> keyStrokes = null;
      for (Enumeration<KeyStroke> keys = bindings.keys(); keys.hasMoreElements(); ) {
        KeyStroke key = keys.nextElement();
        if (bindings.get(key) == a) {
          if (keyStrokes == null) {
            keyStrokes = new Vector<KeyStroke>();
          }
          keyStrokes.addElement(key);
        }
      }
      // See if the parent has any.
      if (parent != null) {
        KeyStroke[] pStrokes = parent.getKeyStrokesForAction(a);
        if (pStrokes != null) {
          // Remove any bindings defined in the parent that
          // are locally defined.
          int rCount = 0;
          for (int counter = pStrokes.length - 1; counter >= 0;
              counter--) {
            if (isLocallyDefined(pStrokes[counter])) {
              pStrokes[counter] = null;
              rCount++;
            }
          }
          if (rCount > 0 && rCount < pStrokes.length) {
            if (keyStrokes == null) {
              keyStrokes = new Vector<KeyStroke>();
            }
            for (int counter = pStrokes.length - 1; counter >= 0;
                counter--) {
              if (pStrokes[counter] != null) {
                keyStrokes.addElement(pStrokes[counter]);
              }
            }
          } else if (rCount == 0) {
            if (keyStrokes == null) {
              retValue = pStrokes;
            } else {
              retValue = new KeyStroke[keyStrokes.size() +
                  pStrokes.length];
              keyStrokes.copyInto(retValue);
              System.arraycopy(pStrokes, 0, retValue,
                  keyStrokes.size(), pStrokes.length);
              keyStrokes = null;
            }
          }
        }
      }
      if (keyStrokes != null) {
        retValue = new KeyStroke[keyStrokes.size()];
        keyStrokes.copyInto(retValue);
      }
      return retValue;
    }

    public boolean isLocallyDefined(KeyStroke key) {
      return bindings.containsKey(key);
    }

    public void addActionForKeyStroke(KeyStroke key, Action a) {
      bindings.put(key, a);
    }

    public void removeKeyStrokeBinding(KeyStroke key) {
      bindings.remove(key);
    }

    public void removeBindings() {
      bindings.clear();
    }

    public Keymap getResolveParent() {
      return parent;
    }

    public void setResolveParent(Keymap parent) {
      this.parent = parent;
    }

    /**
     * String representation of the keymap... potentially
     * a very long string.
     */
    public String toString() {
      return "Keymap[" + nm + "]" + bindings;
    }

    String nm;
    Keymap parent;
    Hashtable<KeyStroke, Action> bindings;
    Action defaultAction;
  }


  /**
   * KeymapWrapper wraps a Keymap inside an InputMap. For KeymapWrapper
   * to be useful it must be used with a KeymapActionMap.
   * KeymapWrapper for the most part, is an InputMap with two parents.
   * The first parent visited is ALWAYS the Keymap, with the second
   * parent being the parent inherited from InputMap. If
   * <code>keymap.getAction</code> returns null, implying the Keymap
   * does not have a binding for the KeyStroke,
   * the parent is then visited. If the Keymap has a binding, the
   * Action is returned, if not and the KeyStroke represents a
   * KeyTyped event and the Keymap has a defaultAction,
   * <code>DefaultActionKey</code> is returned.
   * <p>KeymapActionMap is then able to transate the object passed in
   * to either message the Keymap, or message its default implementation.
   */
  static class KeymapWrapper extends InputMap {

    static final Object DefaultActionKey = new Object();

    private Keymap keymap;

    KeymapWrapper(Keymap keymap) {
      this.keymap = keymap;
    }

    public KeyStroke[] keys() {
      KeyStroke[] sKeys = super.keys();
      KeyStroke[] keymapKeys = keymap.getBoundKeyStrokes();
      int sCount = (sKeys == null) ? 0 : sKeys.length;
      int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length;
      if (sCount == 0) {
        return keymapKeys;
      }
      if (keymapCount == 0) {
        return sKeys;
      }
      KeyStroke[] retValue = new KeyStroke[sCount + keymapCount];
      // There may be some duplication here...
      System.arraycopy(sKeys, 0, retValue, 0, sCount);
      System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount);
      return retValue;
    }

    public int size() {
      // There may be some duplication here...
      KeyStroke[] keymapStrokes = keymap.getBoundKeyStrokes();
      int keymapCount = (keymapStrokes == null) ? 0 :
          keymapStrokes.length;
      return super.size() + keymapCount;
    }

    public Object get(KeyStroke keyStroke) {
      Object retValue = keymap.getAction(keyStroke);
      if (retValue == null) {
        retValue = super.get(keyStroke);
        if (retValue == null &&
            keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED &&
            keymap.getDefaultAction() != null) {
          // Implies this is a KeyTyped event, use the default
          // action.
          retValue = DefaultActionKey;
        }
      }
      return retValue;
    }
  }


  /**
   * Wraps a Keymap inside an ActionMap. This is used with
   * a KeymapWrapper. If <code>get</code> is passed in
   * <code>KeymapWrapper.DefaultActionKey</code>, the default action is
   * returned, otherwise if the key is an Action, it is returned.
   */
  static class KeymapActionMap extends ActionMap {

    private Keymap keymap;

    KeymapActionMap(Keymap keymap) {
      this.keymap = keymap;
    }

    public Object[] keys() {
      Object[] sKeys = super.keys();
      Object[] keymapKeys = keymap.getBoundActions();
      int sCount = (sKeys == null) ? 0 : sKeys.length;
      int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length;
      boolean hasDefault = (keymap.getDefaultAction() != null);
      if (hasDefault) {
        keymapCount++;
      }
      if (sCount == 0) {
        if (hasDefault) {
          Object[] retValue = new Object[keymapCount];
          if (keymapCount > 1) {
            System.arraycopy(keymapKeys, 0, retValue, 0,
                keymapCount - 1);
          }
          retValue[keymapCount - 1] = KeymapWrapper.DefaultActionKey;
          return retValue;
        }
        return keymapKeys;
      }
      if (keymapCount == 0) {
        return sKeys;
      }
      Object[] retValue = new Object[sCount + keymapCount];
      // There may be some duplication here...
      System.arraycopy(sKeys, 0, retValue, 0, sCount);
      if (hasDefault) {
        if (keymapCount > 1) {
          System.arraycopy(keymapKeys, 0, retValue, sCount,
              keymapCount - 1);
        }
        retValue[sCount + keymapCount - 1] = KeymapWrapper.
            DefaultActionKey;
      } else {
        System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount);
      }
      return retValue;
    }

    public int size() {
      // There may be some duplication here...
      Object[] actions = keymap.getBoundActions();
      int keymapCount = (actions == null) ? 0 : actions.length;
      if (keymap.getDefaultAction() != null) {
        keymapCount++;
      }
      return super.size() + keymapCount;
    }

    public Action get(Object key) {
      Action retValue = super.get(key);
      if (retValue == null) {
        // Try the Keymap.
        if (key == KeymapWrapper.DefaultActionKey) {
          retValue = keymap.getDefaultAction();
        } else if (key instanceof Action) {
          // This is a little iffy, technically an Action is
          // a valid Key. We're assuming the Action came from
          // the InputMap though.
          retValue = (Action) key;
        }
      }
      return retValue;
    }
  }

  private static final Object FOCUSED_COMPONENT =
      new StringBuilder("JTextComponent_FocusedComponent");

  /**
   * The default keymap that will be shared by all
   * <code>JTextComponent</code> instances unless they
   * have had a different keymap set.
   */
  public static final String DEFAULT_KEYMAP = "default";

  /**
   * Event to use when firing a notification of change to caret
   * position.  This is mutable so that the event can be reused
   * since caret events can be fairly high in bandwidth.
   */
  static class MutableCaretEvent extends CaretEvent implements ChangeListener, FocusListener,
      MouseListener {

    MutableCaretEvent(JTextComponent c) {
      super(c);
    }

    final void fire() {
      JTextComponent c = (JTextComponent) getSource();
      if (c != null) {
        Caret caret = c.getCaret();
        dot = caret.getDot();
        mark = caret.getMark();
        c.fireCaretUpdate(this);
      }
    }

    public final String toString() {
      return "dot=" + dot + "," + "mark=" + mark;
    }

    // --- CaretEvent methods -----------------------

    public final int getDot() {
      return dot;
    }

    public final int getMark() {
      return mark;
    }

    // --- ChangeListener methods -------------------

    public final void stateChanged(ChangeEvent e) {
      if (!dragActive) {
        fire();
      }
    }

    // --- FocusListener methods -----------------------------------
    public void focusGained(FocusEvent fe) {
      AppContext.getAppContext().put(FOCUSED_COMPONENT,
          fe.getSource());
    }

    public void focusLost(FocusEvent fe) {
    }

    // --- MouseListener methods -----------------------------------

    /**
     * Requests focus on the associated
     * text component, and try to set the cursor position.
     *
     * @param e the mouse event
     * @see MouseListener#mousePressed
     */
    public final void mousePressed(MouseEvent e) {
      dragActive = true;
    }

    /**
     * Called when the mouse is released.
     *
     * @param e the mouse event
     * @see MouseListener#mouseReleased
     */
    public final void mouseReleased(MouseEvent e) {
      dragActive = false;
      fire();
    }

    public final void mouseClicked(MouseEvent e) {
    }

    public final void mouseEntered(MouseEvent e) {
    }

    public final void mouseExited(MouseEvent e) {
    }

    private boolean dragActive;
    private int dot;
    private int mark;
  }

  //
  // Process any input method events that the component itself
  // recognizes. The default on-the-spot handling for input method
  // composed(uncommitted) text is done here after all input
  // method listeners get called for stealing the events.
  //
  protected void processInputMethodEvent(InputMethodEvent e) {
    // let listeners handle the events
    super.processInputMethodEvent(e);

    if (!e.isConsumed()) {
      if (!isEditable()) {
        return;
      } else {
        switch (e.getID()) {
          case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
            replaceInputMethodText(e);

            // fall through

          case InputMethodEvent.CARET_POSITION_CHANGED:
            setInputMethodCaretPosition(e);
            break;
        }
      }

      e.consume();
    }
  }

  //
  // Overrides this method to become an active input method client.
  //
  public InputMethodRequests getInputMethodRequests() {
    if (inputMethodRequestsHandler == null) {
      inputMethodRequestsHandler = new InputMethodRequestsHandler();
      Document doc = getDocument();
      if (doc != null) {
        doc.addDocumentListener((DocumentListener) inputMethodRequestsHandler);
      }
    }

    return inputMethodRequestsHandler;
  }

  //
  // Overrides this method to watch the listener installed.
  //
  public void addInputMethodListener(InputMethodListener l) {
    super.addInputMethodListener(l);
    if (l != null) {
      needToSendKeyTypedEvent = false;
      checkedInputOverride = true;
    }
  }


  //
  // Default implementation of the InputMethodRequests interface.
  //
  class InputMethodRequestsHandler implements InputMethodRequests, DocumentListener {

    // --- InputMethodRequests methods ---

    public AttributedCharacterIterator cancelLatestCommittedText(
        Attribute[] attributes) {
      Document doc = getDocument();
      if ((doc != null) && (latestCommittedTextStart != null)
          && (!latestCommittedTextStart.equals(latestCommittedTextEnd))) {
        try {
          int startIndex = latestCommittedTextStart.getOffset();
          int endIndex = latestCommittedTextEnd.getOffset();
          String latestCommittedText =
              doc.getText(startIndex, endIndex - startIndex);
          doc.remove(startIndex, endIndex - startIndex);
          return new AttributedString(latestCommittedText).getIterator();
        } catch (BadLocationException ble) {
        }
      }
      return null;
    }

    public AttributedCharacterIterator getCommittedText(int beginIndex,
        int endIndex, Attribute[] attributes) {
      int composedStartIndex = 0;
      int composedEndIndex = 0;
      if (composedTextExists()) {
        composedStartIndex = composedTextStart.getOffset();
        composedEndIndex = composedTextEnd.getOffset();
      }

      String committed;
      try {
        if (beginIndex < composedStartIndex) {
          if (endIndex <= composedStartIndex) {
            committed = getText(beginIndex, endIndex - beginIndex);
          } else {
            int firstPartLength = composedStartIndex - beginIndex;
            committed = getText(beginIndex, firstPartLength) +
                getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
          }
        } else {
          committed = getText(beginIndex + (composedEndIndex - composedStartIndex),
              endIndex - beginIndex);
        }
      } catch (BadLocationException ble) {
        throw new IllegalArgumentException("Invalid range");
      }
      return new AttributedString(committed).getIterator();
    }

    public int getCommittedTextLength() {
      Document doc = getDocument();
      int length = 0;
      if (doc != null) {
        length = doc.getLength();
        if (composedTextContent != null) {
          if (composedTextEnd == null
              || composedTextStart == null) {
                        /*
                         * fix for : 6355666
                         * this is the case when this method is invoked
                         * from DocumentListener. At this point
                         * composedTextEnd and composedTextStart are
                         * not defined yet.
                         */
            length -= composedTextContent.length();
          } else {
            length -= composedTextEnd.getOffset() -
                composedTextStart.getOffset();
          }
        }
      }
      return length;
    }

    public int getInsertPositionOffset() {
      int composedStartIndex = 0;
      int composedEndIndex = 0;
      if (composedTextExists()) {
        composedStartIndex = composedTextStart.getOffset();
        composedEndIndex = composedTextEnd.getOffset();
      }
      int caretIndex = getCaretPosition();

      if (caretIndex < composedStartIndex) {
        return caretIndex;
      } else if (caretIndex < composedEndIndex) {
        return composedStartIndex;
      } else {
        return caretIndex - (composedEndIndex - composedStartIndex);
      }
    }

    public TextHitInfo getLocationOffset(int x, int y) {
      if (composedTextAttribute == null) {
        return null;
      } else {
        Point p = getLocationOnScreen();
        p.x = x - p.x;
        p.y = y - p.y;
        int pos = viewToModel(p);
        if ((pos >= composedTextStart.getOffset()) &&
            (pos <= composedTextEnd.getOffset())) {
          return TextHitInfo.leading(pos - composedTextStart.getOffset());
        } else {
          return null;
        }
      }
    }

    public Rectangle getTextLocation(TextHitInfo offset) {
      Rectangle r;

      try {
        r = modelToView(getCaretPosition());
        if (r != null) {
          Point p = getLocationOnScreen();
          r.translate(p.x, p.y);
        }
      } catch (BadLocationException ble) {
        r = null;
      }

      if (r == null) {
        r = new Rectangle();
      }

      return r;
    }

    public AttributedCharacterIterator getSelectedText(
        Attribute[] attributes) {
      String selection = JTextComponent.this.getSelectedText();
      if (selection != null) {
        return new AttributedString(selection).getIterator();
      } else {
        return null;
      }
    }

    // --- DocumentListener methods ---

    public void changedUpdate(DocumentEvent e) {
      latestCommittedTextStart = latestCommittedTextEnd = null;
    }

    public void insertUpdate(DocumentEvent e) {
      latestCommittedTextStart = latestCommittedTextEnd = null;
    }

    public void removeUpdate(DocumentEvent e) {
      latestCommittedTextStart = latestCommittedTextEnd = null;
    }
  }

  //
  // Replaces the current input method (composed) text according to
  // the passed input method event. This method also inserts the
  // committed text into the document.
  //
  private void replaceInputMethodText(InputMethodEvent e) {
    int commitCount = e.getCommittedCharacterCount();
    AttributedCharacterIterator text = e.getText();
    int composedTextIndex;

    // old composed text deletion
    Document doc = getDocument();
    if (composedTextExists()) {
      try {
        doc.remove(composedTextStart.getOffset(),
            composedTextEnd.getOffset() -
                composedTextStart.getOffset());
      } catch (BadLocationException ble) {
      }
      composedTextStart = composedTextEnd = null;
      composedTextAttribute = null;
      composedTextContent = null;
    }

    if (text != null) {
      text.first();
      int committedTextStartIndex = 0;
      int committedTextEndIndex = 0;

      // committed text insertion
      if (commitCount > 0) {
        // Remember latest committed text start index
        committedTextStartIndex = caret.getDot();

        // Need to generate KeyTyped events for the committed text for components
        // that are not aware they are active input method clients.
        if (shouldSynthensizeKeyEvents()) {
          for (char c = text.current(); commitCount > 0;
              c = text.next(), commitCount--) {
            KeyEvent ke = new KeyEvent(this, KeyEvent.KEY_TYPED,
                EventQueue.getMostRecentEventTime(),
                0, KeyEvent.VK_UNDEFINED, c);
            processKeyEvent(ke);
          }
        } else {
          StringBuilder strBuf = new StringBuilder();
          for (char c = text.current(); commitCount > 0;
              c = text.next(), commitCount--) {
            strBuf.append(c);
          }

          // map it to an ActionEvent
          mapCommittedTextToAction(strBuf.toString());
        }

        // Remember latest committed text end index
        committedTextEndIndex = caret.getDot();
      }

      // new composed text insertion
      composedTextIndex = text.getIndex();
      if (composedTextIndex < text.getEndIndex()) {
        createComposedTextAttribute(composedTextIndex, text);
        try {
          replaceSelection(null);
          doc.insertString(caret.getDot(), composedTextContent,
              composedTextAttribute);
          composedTextStart = doc.createPosition(caret.getDot() -
              composedTextContent.length());
          composedTextEnd = doc.createPosition(caret.getDot());
        } catch (BadLocationException ble) {
          composedTextStart = composedTextEnd = null;
          composedTextAttribute = null;
          composedTextContent = null;
        }
      }

      // Save the latest committed text information
      if (committedTextStartIndex != committedTextEndIndex) {
        try {
          latestCommittedTextStart = doc.
              createPosition(committedTextStartIndex);
          latestCommittedTextEnd = doc.
              createPosition(committedTextEndIndex);
        } catch (BadLocationException ble) {
          latestCommittedTextStart =
              latestCommittedTextEnd = null;
        }
      } else {
        latestCommittedTextStart =
            latestCommittedTextEnd = null;
      }
    }
  }

  private void createComposedTextAttribute(int composedIndex,
      AttributedCharacterIterator text) {
    Document doc = getDocument();
    StringBuilder strBuf = new StringBuilder();

    // create attributed string with no attributes
    for (char c = text.setIndex(composedIndex);
        c != CharacterIterator.DONE; c = text.next()) {
      strBuf.append(c);
    }

    composedTextContent = strBuf.toString();
    composedTextAttribute = new SimpleAttributeSet();
    composedTextAttribute.addAttribute(StyleConstants.ComposedTextAttribute,
        new AttributedString(text, composedIndex, text.getEndIndex()));
  }

  /**
   * Saves composed text around the specified position.
   *
   * The composed text (if any) around the specified position is saved
   * in a backing store and removed from the document.
   *
   * @param pos document position to identify the composed text location
   * @return {@code true} if the composed text exists and is saved, {@code false} otherwise
   * @see #restoreComposedText
   * @since 1.7
   */
  protected boolean saveComposedText(int pos) {
    if (composedTextExists()) {
      int start = composedTextStart.getOffset();
      int len = composedTextEnd.getOffset() -
          composedTextStart.getOffset();
      if (pos >= start && pos <= start + len) {
        try {
          getDocument().remove(start, len);
          return true;
        } catch (BadLocationException ble) {
        }
      }
    }
    return false;
  }

  /**
   * Restores composed text previously saved by {@code saveComposedText}.
   *
   * The saved composed text is inserted back into the document. This method
   * should be invoked only if {@code saveComposedText} returns {@code true}.
   *
   * @see #saveComposedText
   * @since 1.7
   */
  protected void restoreComposedText() {
    Document doc = getDocument();
    try {
      doc.insertString(caret.getDot(),
          composedTextContent,
          composedTextAttribute);
      composedTextStart = doc.createPosition(caret.getDot() -
          composedTextContent.length());
      composedTextEnd = doc.createPosition(caret.getDot());
    } catch (BadLocationException ble) {
    }
  }

  //
  // Map committed text to an ActionEvent. If the committed text length is 1,
  // treat it as a KeyStroke, otherwise or there is no KeyStroke defined,
  // treat it just as a default action.
  //
  private void mapCommittedTextToAction(String committedText) {
    Keymap binding = getKeymap();
    if (binding != null) {
      Action a = null;
      if (committedText.length() == 1) {
        KeyStroke k = KeyStroke.getKeyStroke(committedText.charAt(0));
        a = binding.getAction(k);
      }

      if (a == null) {
        a = binding.getDefaultAction();
      }

      if (a != null) {
        ActionEvent ae =
            new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
                committedText,
                EventQueue.getMostRecentEventTime(),
                getCurrentEventModifiers());
        a.actionPerformed(ae);
      }
    }
  }

  //
  // Sets the caret position according to the passed input method
  // event. Also, sets/resets composed text caret appropriately.
  //
  private void setInputMethodCaretPosition(InputMethodEvent e) {
    int dot;

    if (composedTextExists()) {
      dot = composedTextStart.getOffset();
      if (!(caret instanceof ComposedTextCaret)) {
        if (composedTextCaret == null) {
          composedTextCaret = new ComposedTextCaret();
        }
        originalCaret = caret;
        // Sets composed text caret
        exchangeCaret(originalCaret, composedTextCaret);
      }

      TextHitInfo caretPos = e.getCaret();
      if (caretPos != null) {
        int index = caretPos.getInsertionIndex();
        dot += index;
        if (index == 0) {
          // Scroll the component if needed so that the composed text
          // becomes visible.
          try {
            Rectangle d = modelToView(dot);
            Rectangle end = modelToView(composedTextEnd.getOffset());
            Rectangle b = getBounds();
            d.x += Math.min(end.x - d.x, b.width);
            scrollRectToVisible(d);
          } catch (BadLocationException ble) {
          }
        }
      }
      caret.setDot(dot);
    } else if (caret instanceof ComposedTextCaret) {
      dot = caret.getDot();
      // Restores original caret
      exchangeCaret(caret, originalCaret);
      caret.setDot(dot);
    }
  }

  private void exchangeCaret(Caret oldCaret, Caret newCaret) {
    int blinkRate = oldCaret.getBlinkRate();
    setCaret(newCaret);
    caret.setBlinkRate(blinkRate);
    caret.setVisible(hasFocus());
  }

  /**
   * Returns true if KeyEvents should be synthesized from an InputEvent.
   */
  private boolean shouldSynthensizeKeyEvents() {
    if (!checkedInputOverride) {
      // Checks whether the client code overrides processInputMethodEvent.
      // If it is overridden, need not to generate KeyTyped events for committed text.
      // If it's not, behave as an passive input method client.
      needToSendKeyTypedEvent = !METHOD_OVERRIDDEN.get(getClass());
      checkedInputOverride = true;
    }
    return needToSendKeyTypedEvent;
  }

  //
  // Checks whether a composed text in this text component
  //
  boolean composedTextExists() {
    return (composedTextStart != null);
  }

  //
  // Caret implementation for editing the composed text.
  //
  class ComposedTextCaret extends DefaultCaret implements Serializable {

    Color bg;

    //
    // Get the background color of the component
    //
    public void install(JTextComponent c) {
      super.install(c);

      Document doc = c.getDocument();
      if (doc instanceof StyledDocument) {
        StyledDocument sDoc = (StyledDocument) doc;
        Element elem = sDoc.getCharacterElement(c.composedTextStart.getOffset());
        AttributeSet attr = elem.getAttributes();
        bg = sDoc.getBackground(attr);
      }

      if (bg == null) {
        bg = c.getBackground();
      }
    }

    //
    // Draw caret in XOR mode.
    //
    public void paint(Graphics g) {
      if (isVisible()) {
        try {
          Rectangle r = component.modelToView(getDot());
          g.setXORMode(bg);
          g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
          g.setPaintMode();
        } catch (BadLocationException e) {
          // can't render I guess
          //System.err.println("Can't render cursor");
        }
      }
    }

    //
    // If some area other than the composed text is clicked by mouse,
    // issue endComposition() to force commit the composed text.
    //
    protected void positionCaret(MouseEvent me) {
      JTextComponent host = component;
      Point pt = new Point(me.getX(), me.getY());
      int offset = host.viewToModel(pt);
      int composedStartIndex = host.composedTextStart.getOffset();
      if ((offset < composedStartIndex) ||
          (offset > composedTextEnd.getOffset())) {
        try {
          // Issue endComposition
          Position newPos = host.getDocument().createPosition(offset);
          host.getInputContext().endComposition();

          // Post a caret positioning runnable to assure that the positioning
          // occurs *after* committing the composed text.
          EventQueue.invokeLater(new DoSetCaretPosition(host, newPos));
        } catch (BadLocationException ble) {
          System.err.println(ble);
        }
      } else {
        // Normal processing
        super.positionCaret(me);
      }
    }
  }

  //
  // Runnable class for invokeLater() to set caret position later.
  //
  private class DoSetCaretPosition implements Runnable {

    JTextComponent host;
    Position newPos;

    DoSetCaretPosition(JTextComponent host, Position newPos) {
      this.host = host;
      this.newPos = newPos;
    }

    public void run() {
      host.setCaretPosition(newPos.getOffset());
    }
  }
}
